Skip to content

Commit

Permalink
Merge pull request #332 from 1inch/feature/fee/flow-1
Browse files Browse the repository at this point in the history
Fee flow 1: getters without FeeBank
  • Loading branch information
ZumZoom authored Aug 19, 2024
2 parents 6f5dcb3 + 83f032f commit 32c3cbf
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 42 deletions.
151 changes: 128 additions & 23 deletions contracts/extensions/FeeTaker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { SafeERC20 } from "@1inch/solidity-utils/contracts/libraries/SafeERC20.s
import { UniERC20 } from "@1inch/solidity-utils/contracts/libraries/UniERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";

import { IAmountGetter } from "../interfaces/IAmountGetter.sol";
import { IOrderMixin } from "../interfaces/IOrderMixin.sol";
import { IPostInteraction } from "../interfaces/IPostInteraction.sol";
import { MakerTraits, MakerTraitsLib } from "../libraries/MakerTraitsLib.sol";

/// @title Helper contract that adds feature of collecting fee in takerAsset
contract FeeTaker is IPostInteraction, Ownable {
contract FeeTaker is IPostInteraction, IAmountGetter, Ownable {
using AddressLib for Address;
using SafeERC20 for IERC20;
using UniERC20 for IERC20;
Expand Down Expand Up @@ -55,46 +57,111 @@ contract FeeTaker is IPostInteraction, Ownable {
*/
receive() external payable {}

/**
* @dev Calculate makingAmount with fee.
* `extraData` consists of:
* 2 bytes — integrator fee percentage (in 1e5)
* 2 bytes — resolver fee percentage (in 1e5)
* 1 byte - taker whitelist size
* (bytes10)[N] — taker whitelist
*/
function getMakingAmount(
IOrderMixin.Order calldata order,
bytes calldata extension,
bytes32 orderHash,
address taker,
uint256 takingAmount,
uint256 remainingMakingAmount,
bytes calldata extraData
) external view returns (uint256 calculatedMakingAmount) {
unchecked {
(uint256 integratorFee, uint256 resolverFee, bytes calldata tail) = _parseFeeData(extraData, taker);
if (tail.length > 20) {
calculatedMakingAmount = IAmountGetter(address(bytes20(tail))).getMakingAmount(
order, extension, orderHash, taker, takingAmount, remainingMakingAmount, tail[20:]
);
} else {
calculatedMakingAmount = order.makingAmount;
}
calculatedMakingAmount = Math.mulDiv(calculatedMakingAmount, _FEE_BASE, _FEE_BASE + integratorFee + resolverFee, Math.Rounding.Floor);
return Math.mulDiv(calculatedMakingAmount, takingAmount, order.takingAmount, Math.Rounding.Floor);
}
}

/**
* @dev Calculate takingAmount with fee.
* `extraData` consists of:
* 2 bytes — integrator fee percentage (in 1e5)
* 2 bytes — resolver fee percentage (in 1e5)
* 1 byte - taker whitelist size
* (bytes10)[N] — taker whitelist
*/
function getTakingAmount(
IOrderMixin.Order calldata order,
bytes calldata extension,
bytes32 orderHash,
address taker,
uint256 makingAmount,
uint256 remainingMakingAmount,
bytes calldata extraData
) external view returns (uint256 calculatedTakingAmount) {
unchecked {
(uint256 integratorFee, uint256 resolverFee, bytes calldata tail) = _parseFeeData(extraData, taker);
if (tail.length > 20) {
calculatedTakingAmount = IAmountGetter(address(bytes20(tail))).getTakingAmount(
order, extension, orderHash, taker, makingAmount, remainingMakingAmount, tail[20:]
);
} else {
calculatedTakingAmount = order.takingAmount;
}
calculatedTakingAmount = Math.mulDiv(calculatedTakingAmount, _FEE_BASE + integratorFee + resolverFee, _FEE_BASE, Math.Rounding.Ceil);
return Math.mulDiv(calculatedTakingAmount, makingAmount, order.makingAmount, Math.Rounding.Ceil);
}
}

/**
* @notice See {IPostInteraction-postInteraction}.
* @dev Takes the fee in taking tokens and transfers the rest to the maker.
* `extraData` consists of:
* 2 bytes — fee percentage (in 1e5)
* 2 bytes — integrator fee percentage (in 1e5)
* 2 bytes — resolver fee percentage (in 1e5)
* 1 byte - taker whitelist size
* (bytes10)[N] — taker whitelist
* 20 bytes — fee recipient
* 20 bytes — receiver of taking tokens (optional, if not set, maker is used)
*/
function postInteraction(
IOrderMixin.Order calldata order,
bytes calldata /* extension */,
bytes32 /* orderHash */,
address /* taker */,
address taker,
uint256 /* makingAmount */,
uint256 takingAmount,
uint256 /* remainingMakingAmount */,
bytes calldata extraData
) external onlyLimitOrderProtocol {
uint256 fee = takingAmount * uint256(uint16(bytes2(extraData))) / _FEE_BASE;
address feeRecipient = address(bytes20(extraData[2:22]));

address receiver = order.maker.get();
if (extraData.length > 22) {
receiver = address(bytes20(extraData[22:42]));
}

bool isEth = order.takerAsset.get() == address(_WETH) && order.makerTraits.unwrapWeth();

if (isEth) {
if (fee > 0) {
_sendEth(feeRecipient, fee);
unchecked {
(uint256 integratorFee, uint256 resolverFee, bytes calldata tail) = _parseFeeData(extraData, taker);
address feeRecipient = address(bytes20(tail));
tail = tail[20:];
uint256 denominator = _FEE_BASE + integratorFee + resolverFee;
// fee is calculated as a sum of separate fees to limit rounding errors
uint256 fee = Math.mulDiv(takingAmount, integratorFee, denominator) + Math.mulDiv(takingAmount, resolverFee, denominator);

address receiver = order.maker.get();
if (tail.length > 0) {
receiver = address(bytes20(tail));
}
unchecked {

if (order.takerAsset.get() == address(_WETH) && order.makerTraits.unwrapWeth()) {
if (fee > 0) {
_sendEth(feeRecipient, fee);
}
_sendEth(receiver, takingAmount - fee);
}
} else {
if (fee > 0) {
IERC20(order.takerAsset.get()).safeTransfer(feeRecipient, fee);
}
unchecked {
} else {
if (fee > 0) {
IERC20(order.takerAsset.get()).safeTransfer(feeRecipient, fee);
}
IERC20(order.takerAsset.get()).safeTransfer(receiver, takingAmount - fee);
}
}
Expand All @@ -115,4 +182,42 @@ contract FeeTaker is IPostInteraction, Ownable {
revert EthTransferFailed();
}
}

/**
* @dev Validates whether the resolver is whitelisted.
* @param whitelist Whitelist is tightly packed struct of the following format:
* ```
* (bytes10)[N] resolversAddresses;
* ```
* Only 10 lowest bytes of the resolver address are used for comparison.
* @param resolver The resolver to check.
* @return Whether the resolver is whitelisted.
*/
function _isWhitelisted(bytes calldata whitelist, address resolver) private pure returns (bool) {
unchecked {
uint80 maskedResolverAddress = uint80(uint160(resolver));
uint256 size = whitelist.length / 10;
for (uint256 i = 0; i < size; i++) {
uint80 whitelistedAddress = uint80(bytes10(whitelist[:10]));
if (maskedResolverAddress == whitelistedAddress) {
return true;
}
whitelist = whitelist[10:];
}
return false;
}
}

function _parseFeeData(bytes calldata extraData, address taker) private pure returns (uint256 integratorFee, uint256 resolverFee, bytes calldata tail) {
unchecked {
integratorFee = uint256(uint16(bytes2(extraData)));
resolverFee = uint256(uint16(bytes2(extraData[2:])));
uint256 whitelistEnd = 5 + 10 * uint256(uint8(extraData[4]));
bytes calldata whitelist = extraData[5:whitelistEnd];
if (!_isWhitelisted(whitelist, taker)) {
resolverFee *= 2;
}
tail = extraData[whitelistEnd:];
}
}
}
Loading

0 comments on commit 32c3cbf

Please sign in to comment.