Skip to content

Commit

Permalink
Merge pull request #152 from 1inch/feat/new-integrator-fee-flow
Browse files Browse the repository at this point in the history
new integrator fees flow + support for eth fees
  • Loading branch information
ZumZoom authored Apr 12, 2024
2 parents 7986068 + 9cd284f commit 0270f92
Show file tree
Hide file tree
Showing 14 changed files with 250 additions and 57 deletions.
20 changes: 10 additions & 10 deletions contracts/FeeBank.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ contract FeeBank is IFeeBank, Ownable {

error ZeroAddress();

IERC20 private immutable _TOKEN;
IERC20 private immutable _FEE_TOKEN;
IFeeBankCharger private immutable _CHARGER;

mapping(address account => uint256 availableCredit) private _accountDeposits;

constructor(IFeeBankCharger charger_, IERC20 inch_, address owner_) Ownable(owner_) {
if (address(inch_) == address(0)) revert ZeroAddress();
_CHARGER = charger_;
_TOKEN = inch_;
constructor(IFeeBankCharger charger, IERC20 feeToken, address owner) Ownable(owner) {
if (address(feeToken) == address(0)) revert ZeroAddress();
_CHARGER = charger;
_FEE_TOKEN = feeToken;
}

/**
Expand Down Expand Up @@ -66,7 +66,7 @@ contract FeeBank is IFeeBank, Ownable {
uint256 amount,
bytes calldata permit
) public returns (uint256) {
_TOKEN.safePermit(permit);
_FEE_TOKEN.safePermit(permit);
return _depositFor(account, amount);
}

Expand Down Expand Up @@ -100,14 +100,14 @@ contract FeeBank is IFeeBank, Ownable {
totalAccountFees += accountDeposit - availableCredit_; // overflow is impossible due to checks in FeeBankCharger
}
}
_TOKEN.safeTransfer(msg.sender, totalAccountFees);
_FEE_TOKEN.safeTransfer(msg.sender, totalAccountFees);
}

function _depositFor(address account, uint256 amount) internal returns (uint256 totalAvailableCredit) {
if (account == address(0)) revert ZeroAddress();
_TOKEN.safeTransferFrom(msg.sender, address(this), amount);
_FEE_TOKEN.safeTransferFrom(msg.sender, address(this), amount);
unchecked {
_accountDeposits[account] += amount; // overflow is impossible due to limited _TOKEN supply
_accountDeposits[account] += amount; // overflow is impossible due to limited _FEE_TOKEN supply
}
totalAvailableCredit = _CHARGER.increaseAvailableCredit(account, amount);
}
Expand All @@ -117,6 +117,6 @@ contract FeeBank is IFeeBank, Ownable {
unchecked {
_accountDeposits[msg.sender] -= amount; // underflow is impossible due to checks in FeeBankCharger
}
_TOKEN.safeTransfer(account, amount);
_FEE_TOKEN.safeTransfer(account, amount);
}
}
4 changes: 2 additions & 2 deletions contracts/FeeBankCharger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ contract FeeBankCharger is IFeeBankCharger {
_;
}

constructor(IERC20 token) {
FEE_BANK = new FeeBank(this, token, msg.sender);
constructor(IERC20 feeToken) {
FEE_BANK = new FeeBank(this, feeToken, msg.sender);
}

/**
Expand Down
4 changes: 3 additions & 1 deletion contracts/Settlement.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import { SimpleSettlement } from "./SimpleSettlement.sol";
contract Settlement is SimpleSettlement {
error InvalidPriorityFee();

constructor(address limitOrderProtocol, IERC20 token) SimpleSettlement(limitOrderProtocol, token) {}
constructor(address limitOrderProtocol, IERC20 feeToken, address weth, address owner)
SimpleSettlement(limitOrderProtocol, feeToken, weth, owner)
{}

function _postInteraction(
IOrderMixin.Order calldata order,
Expand Down
13 changes: 10 additions & 3 deletions contracts/SimpleSettlement.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
pragma solidity 0.8.23;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IOrderMixin } from "@1inch/limit-order-protocol-contract/contracts/interfaces/IOrderMixin.sol";
import { BaseExtension } from "./extensions/BaseExtension.sol";
import { IntegratorFeeExtension } from "./extensions/IntegratorFeeExtension.sol";
import { ResolverFeeExtension } from "./extensions/ResolverFeeExtension.sol";
import { WhitelistExtension } from "./extensions/WhitelistExtension.sol";


/**
* @title Simple Settlement contract
* @notice Contract to execute limit orders settlement, created by Fusion mode.
Expand All @@ -18,9 +18,16 @@ contract SimpleSettlement is WhitelistExtension, ResolverFeeExtension, Integrato
/**
* @notice Initializes the contract.
* @param limitOrderProtocol The limit order protocol contract.
* @param token The token to charge protocol fees in.
* @param feeToken The token to charge protocol fees in.
* @param weth The WETH address.
* @param owner The owner of the contract.
*/
constructor(address limitOrderProtocol, IERC20 token) BaseExtension(limitOrderProtocol) ResolverFeeExtension(token) {}
constructor(address limitOrderProtocol, IERC20 feeToken, address weth, address owner)
BaseExtension(limitOrderProtocol)
ResolverFeeExtension(feeToken)
IntegratorFeeExtension(weth)
Ownable(owner)
{}

function _postInteraction(
IOrderMixin.Order calldata order,
Expand Down
10 changes: 10 additions & 0 deletions contracts/extensions/ExtensionLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pragma solidity ^0.8.0;
library ExtensionLib {
bytes1 private constant _RESOLVER_FEE_FLAG = 0x01;
bytes1 private constant _INTEGRATOR_FEE_FLAG = 0x02;
bytes1 private constant _CUSTOM_RECEIVER_FLAG = 0x04;
uint256 private constant _WHITELIST_SHIFT = 3;

/**
Expand All @@ -29,6 +30,15 @@ library ExtensionLib {
return extraData[extraData.length - 1] & _INTEGRATOR_FEE_FLAG == _INTEGRATOR_FEE_FLAG;
}

/**
* @notice Checks if the custom receiver is enabled
* @param extraData Data to be processed in the extension
* @return True if the custom receiver is specified
*/
function hasCustomReceiver(bytes calldata extraData) internal pure returns (bool) {
return extraData[extraData.length - 1] & _CUSTOM_RECEIVER_FLAG == _CUSTOM_RECEIVER_FLAG;
}

/**
* @notice Gets the number of resolvers in the whitelist
* @param extraData Data to be processed in the extension
Expand Down
81 changes: 71 additions & 10 deletions contracts/extensions/IntegratorFeeExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,51 @@
pragma solidity 0.8.23;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IOrderMixin } from "@1inch/limit-order-protocol-contract/contracts/interfaces/IOrderMixin.sol";
import { MakerTraits, MakerTraitsLib } from "@1inch/limit-order-protocol-contract/contracts/libraries/MakerTraitsLib.sol";
import { SafeERC20 } from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol";
import { Address, AddressLib } from "@1inch/solidity-utils/contracts/libraries/AddressLib.sol";
import { UniERC20 } from "@1inch/solidity-utils/contracts/libraries/UniERC20.sol";
import { BaseExtension } from "./BaseExtension.sol";
import { ExtensionLib } from "./ExtensionLib.sol";

/**
* @title Integrator Fee Extension
* @notice Abstract contract designed to integrate fee processing within the post-interaction phase of order execution.
*/
abstract contract IntegratorFeeExtension is BaseExtension {
abstract contract IntegratorFeeExtension is BaseExtension, Ownable {
using SafeERC20 for IERC20;
using AddressLib for Address;
using ExtensionLib for bytes;
using MakerTraitsLib for MakerTraits;
using UniERC20 for IERC20;

uint256 private constant _TAKING_FEE_BASE = 1e9;
/**
* @dev Eth transfer failed. The target fallback may have reverted.
*/
error EthTransferFailed();

/// @dev Allows fees in range [1e-5, 0.65535]
uint256 private constant _FEE_BASE = 1e5;

address private immutable _WETH;

constructor(address weth) {
_WETH = weth;
}

/**
* @notice Fallback function to receive ETH.
*/
receive() external payable {}

/**
* @param extraData Structured data of length n bytes, segmented as follows:
* [0:20] - Integrator address.
* [20:24] - Integration fee information.
* [24:n] - ExtraData for other extensions, not utilized by this integration fee extension.
* [0:2] - Fee percentage in basis points.
* [2:22] - Integrator address.
* [22:42] - Custom receiver address.
* [42:n] - ExtraData for other extensions, not utilized by this integration fee extension.
* [n] - Bitmap indicating usage flags, where `xxxx xx1x` signifies integration fee usage. Other bits in this bitmap are not used by this extension.
*/
function _postInteraction(
Expand All @@ -38,13 +61,51 @@ abstract contract IntegratorFeeExtension is BaseExtension {
bytes calldata extraData
) internal virtual override {
if (extraData.integratorFeeEnabled()) {
address integrator = address(bytes20(extraData[:20]));
uint256 fee = takingAmount * uint256(uint32(bytes4(extraData[20:24]))) / _TAKING_FEE_BASE;
if (fee > 0) {
IERC20(order.takerAsset.get()).safeTransferFrom(taker, integrator, fee);
uint256 fee = takingAmount * uint256(uint16(bytes2(extraData))) / _FEE_BASE;
address feeRecipient = address(bytes20(extraData[2:22]));
extraData = extraData[22:];

address receiver = order.maker.get();
if (extraData.hasCustomReceiver()) {
receiver = address(bytes20(extraData));
extraData = extraData[20:];
}

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

if (isEth) {
if (fee > 0) {
_sendEth(feeRecipient, fee);
}
unchecked {
_sendEth(receiver, takingAmount - fee);
}
} else {
if (fee > 0) {
IERC20(order.takerAsset.get()).safeTransfer(feeRecipient, fee);
}
unchecked {
IERC20(order.takerAsset.get()).safeTransfer(receiver, takingAmount - fee);
}
}
extraData = extraData[24:];
}

super._postInteraction(order, extension, orderHash, taker, makingAmount, takingAmount, remainingMakingAmount, extraData);
}

/**
* @notice Retrieves funds accidently sent directly to the contract address
* @param token ERC20 token to retrieve
* @param amount amount to retrieve
*/
function rescueFunds(IERC20 token, uint256 amount) external onlyOwner {
token.uniTransfer(payable(msg.sender), amount);
}

function _sendEth(address target, uint256 amount) private {
(bool success, ) = target.call{value: amount}("");
if (!success) {
revert EthTransferFailed();
}
}
}
2 changes: 1 addition & 1 deletion contracts/extensions/ResolverFeeExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ abstract contract ResolverFeeExtension is BaseExtension, FeeBankCharger {

uint256 private constant _ORDER_FEE_BASE_POINTS = 1e15;

constructor(IERC20 token) FeeBankCharger(token) {}
constructor(IERC20 feeToken) FeeBankCharger(feeToken) {}

/**
* @dev Calculates the resolver fee.
Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/SettlementMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Settlement } from "../Settlement.sol";

contract SettlementMock is Settlement {
constructor(address limitOrderProtocol, IERC20 token)
Settlement(limitOrderProtocol, token)
constructor(address limitOrderProtocol, IERC20 token, address weth)
Settlement(limitOrderProtocol, token, weth, msg.sender)
{}

function decreaseAvailableCreditMock(address account, uint256 amount) external {
Expand Down
4 changes: 2 additions & 2 deletions test/FeeBank.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { expect, ether, getPermit, deployContract } = require('@1inch/solidity-utils');
const { expect, ether, getPermit, deployContract, constants } = require('@1inch/solidity-utils');
const { ethers } = require('hardhat');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
Expand All @@ -11,7 +11,7 @@ describe('FeeBank', function () {

const inch = await deployContract('ERC20PermitMock', ['1INCH', '1INCH', owner, ether('1000')]);
const { lopv4 } = await deploySwapTokens();
const matcher = await deployContract('SettlementMock', [lopv4, inch]);
const matcher = await deployContract('SettlementMock', [lopv4, inch, constants.ZERO_ADDRESS]);

const FeeBank = await ethers.getContractFactory('FeeBank');
const feeBank = FeeBank.attach(await matcher.FEE_BANK());
Expand Down
4 changes: 2 additions & 2 deletions test/MeasureGas.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('MeasureGas', function () {
await weth.deposit({ value: ether('1') });
await weth.connect(alice).deposit({ value: ether('1') });

const settlementExtension = await deployContract('Settlement', [lopv4, inch]);
const settlementExtension = await deployContract('Settlement', [lopv4, inch, weth, owner]);
const SettlementV1 = JSON.parse(fs.readFileSync(path.join(__dirname, '../artifacts-v1/SettlementV1.json'), 'utf8'));
// const settlement = await deployContract(SettlementV1.abi, [lopv3.address, inch.address]);
const ContractFactory = await ethers.getContractFactory(SettlementV1.abi, SettlementV1.bytecode);
Expand Down Expand Up @@ -228,7 +228,7 @@ describe('MeasureGas', function () {
describe('Extension check', function () {
it('post interaction', async function () {
const { contracts: { dai, weth }, accounts: { owner } } = await loadFixture(initContractsAndApproves);
const settlementExtension = await deployContract('Settlement', [owner, weth]);
const settlementExtension = await deployContract('Settlement', [owner, weth, weth, owner]);
const currentTime = (await time.latest()) - time.duration.minutes(1);

const postInteractionData = ethers.solidityPacked(
Expand Down
2 changes: 1 addition & 1 deletion test/PriorityFeeLimiter.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('PriorityFeeLimiter', function () {

async function prepare() {
const { contracts: { dai, weth }, accounts: { owner } } = await initContractsForSettlement();
const settlementExtension = await deployContract('Settlement', [owner, weth]);
const settlementExtension = await deployContract('Settlement', [owner, weth, weth, owner]);
const currentTime = (await time.latest()) - time.duration.minutes(1);

const postInteractionData = ethers.solidityPacked(
Expand Down
Loading

0 comments on commit 0270f92

Please sign in to comment.