Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fee flow 1: getters without FeeBank #332

Merged
merged 7 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading