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

[SC-1366, SC-1368] Update fee collector logic #345

Merged
merged 3 commits into from
Dec 24, 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
37 changes: 24 additions & 13 deletions contracts/extensions/AmountGetterWithFee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import { AmountGetterBase } from "./AmountGetterBase.sol";
/// @title Price getter contract that adds fee calculation
contract AmountGetterWithFee is AmountGetterBase {
/// @dev Allows fees in range [1e-5, 0.65535]
uint256 internal constant _FEE_BASE = 1e5;
uint256 internal constant _DISCOUNT_BASE = 100;
uint256 internal constant _BASE_1E5 = 1e5;
uint256 internal constant _BASE_1E2 = 100;

error InvalidIntegratorFee();
error InvalidIntegratorShare();
error InvalidResolverFee();
error InvalidWhitelistDiscountNumerator();

/**
* @dev Calculates makingAmount with fee.
Expand All @@ -26,11 +31,11 @@ contract AmountGetterWithFee is AmountGetterBase {
bytes calldata extraData
) internal view virtual override returns (uint256) {
unchecked {
(, uint256 integratorFee, uint256 resolverFee, bytes calldata tail) = _parseFeeData(extraData, taker, _isWhitelistedGetterImpl);
(, uint256 integratorFee, , uint256 resolverFee, bytes calldata tail) = _parseFeeData(extraData, taker, _isWhitelistedGetterImpl);
return Math.mulDiv(
super._getMakingAmount(order, extension, orderHash, taker, takingAmount, remainingMakingAmount, tail),
_FEE_BASE,
_FEE_BASE + integratorFee + resolverFee
_BASE_1E5,
_BASE_1E5 + integratorFee + resolverFee
);
}
}
Expand All @@ -48,11 +53,11 @@ contract AmountGetterWithFee is AmountGetterBase {
bytes calldata extraData
) internal view virtual override returns (uint256) {
unchecked {
(, uint256 integratorFee, uint256 resolverFee, bytes calldata tail) = _parseFeeData(extraData, taker, _isWhitelistedGetterImpl);
(, uint256 integratorFee, , uint256 resolverFee, bytes calldata tail) = _parseFeeData(extraData, taker, _isWhitelistedGetterImpl);
return Math.mulDiv(
super._getTakingAmount(order, extension, orderHash, taker, makingAmount, remainingMakingAmount, tail),
_FEE_BASE + integratorFee + resolverFee,
_FEE_BASE,
_BASE_1E5 + integratorFee + resolverFee,
_BASE_1E5,
Math.Rounding.Ceil
);
}
Expand All @@ -61,6 +66,7 @@ contract AmountGetterWithFee is AmountGetterBase {
/**
* @dev `extraData` consists of:
* 2 bytes — integrator fee percentage (in 1e5)
* 1 byte - integrator share percentage (in 1e2)
* 2 bytes — resolver fee percentage (in 1e5)
* 1 byte - whitelist discount numerator (in 1e2)
* bytes — whitelist structure determined by `_isWhitelisted` implementation
Expand All @@ -71,14 +77,19 @@ contract AmountGetterWithFee is AmountGetterBase {
bytes calldata extraData,
address taker,
function (bytes calldata, address) internal view returns (bool, bytes calldata) _isWhitelisted
) internal view returns (bool isWhitelisted, uint256 integratorFee, uint256 resolverFee, bytes calldata tail) {
) internal view returns (bool isWhitelisted, uint256 integratorFee, uint256 integratorShare, uint256 resolverFee, bytes calldata tail) {
unchecked {
integratorFee = uint256(uint16(bytes2(extraData)));
resolverFee = uint256(uint16(bytes2(extraData[2:])));
uint256 whitelistDiscountNumerator = uint256(uint8(bytes1(extraData[4:])));
(isWhitelisted, tail) = _isWhitelisted(extraData[5:], taker);
if (integratorFee > _BASE_1E5) revert InvalidIntegratorFee();
integratorShare = uint256(uint8(bytes1(extraData[2:])));
if (integratorShare > _BASE_1E2) revert InvalidIntegratorShare();
resolverFee = uint256(uint16(bytes2(extraData[3:])));
if (resolverFee > _BASE_1E5) revert InvalidResolverFee();
uint256 whitelistDiscountNumerator = uint256(uint8(bytes1(extraData[5:])));
if (whitelistDiscountNumerator > _BASE_1E2) revert InvalidWhitelistDiscountNumerator();
(isWhitelisted, tail) = _isWhitelisted(extraData[6:], taker);
if (isWhitelisted) {
resolverFee = resolverFee * whitelistDiscountNumerator / _DISCOUNT_BASE;
resolverFee = resolverFee * whitelistDiscountNumerator / _BASE_1E2;
}
}
}
Expand Down
43 changes: 29 additions & 14 deletions contracts/extensions/FeeTaker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,11 @@ contract FeeTaker is IPostInteraction, AmountGetterWithFee, Ownable {
* @dev Takes the fee in taking tokens and transfers the rest to the maker.
* `extraData` consists of:
* 1 byte - flags
* 20 bytes — fee recipient
* 20 bytes — integrator fee recipient
* 20 bytes - protocol fee recipient
* 20 bytes — receiver of taking tokens (optional, if not set, maker is used)
* 2 bytes — integrator fee percentage (in 1e5)
* 1 bytes - integrator rev share percentage (in 1e2)
* 2 bytes — resolver fee percentage (in 1e5)
* bytes — whitelist structure determined by `_isWhitelistedPostInteractionImpl` implementation
* bytes — custom data to call extra postInteraction (optional)
Expand All @@ -121,34 +123,47 @@ contract FeeTaker is IPostInteraction, AmountGetterWithFee, Ownable {
) internal virtual {
unchecked {
bool customReceiver = extraData[0] & _CUSTOM_RECEIVER_FLAG == _CUSTOM_RECEIVER_FLAG;
address feeRecipient = address(bytes20(extraData[1:21]));
extraData = extraData[21:];
address integratorFeeRecipient = address(bytes20(extraData[1:21]));
address protocolFeeRecipient = address(bytes20(extraData[21:41]));
extraData = extraData[41:];

address receiver = order.maker.get();
if (customReceiver) {
receiver = address(bytes20(extraData));
extraData = extraData[20:];
}
(bool isWhitelisted, uint256 integratorFee, uint256 resolverFee, bytes calldata tail) = _parseFeeData(extraData, taker, _isWhitelistedPostInteractionImpl);
(bool isWhitelisted, uint256 integratorFee, uint256 integratorShare, uint256 resolverFee, bytes calldata tail) = _parseFeeData(extraData, taker, _isWhitelistedPostInteractionImpl);
if (!isWhitelisted && _ACCESS_TOKEN.balanceOf(taker) == 0) revert OnlyWhitelistOrAccessToken();

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);
uint256 integratorFeeAmount;
uint256 protocolFeeAmount;

{
uint256 denominator = _BASE_1E5 + integratorFee + resolverFee;
uint256 integratorFeeTotal = Math.mulDiv(takingAmount, integratorFee, denominator);
integratorFeeAmount = Math.mulDiv(integratorFeeTotal, integratorShare, _BASE_1E2);
protocolFeeAmount = Math.mulDiv(takingAmount, resolverFee, denominator) + integratorFeeTotal - integratorFeeAmount;
}

if (order.receiver.get() == address(this)) {
if (order.takerAsset.get() == address(_WETH) && order.makerTraits.unwrapWeth()) {
if (fee > 0) {
_sendEth(feeRecipient, fee);
if (integratorFeeAmount > 0) {
_sendEth(integratorFeeRecipient, integratorFeeAmount);
}
_sendEth(receiver, takingAmount - fee);
if (protocolFeeAmount > 0) {
_sendEth(protocolFeeRecipient, protocolFeeAmount);
}
_sendEth(receiver, takingAmount - integratorFeeAmount - protocolFeeAmount);
} else {
if (fee > 0) {
IERC20(order.takerAsset.get()).safeTransfer(feeRecipient, fee);
if (integratorFeeAmount > 0) {
IERC20(order.takerAsset.get()).safeTransfer(integratorFeeRecipient, integratorFeeAmount);
}
if (protocolFeeAmount > 0) {
IERC20(order.takerAsset.get()).safeTransfer(protocolFeeRecipient, protocolFeeAmount);
}
IERC20(order.takerAsset.get()).safeTransfer(receiver, takingAmount - fee);
IERC20(order.takerAsset.get()).safeTransfer(receiver, takingAmount - integratorFeeAmount - protocolFeeAmount);
}
} else if (fee > 0) {
} else if (integratorFeeAmount + protocolFeeAmount > 0) {
revert InconsistentFee();
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@1inch/limit-order-protocol-contract",
"version": "4.2.1",
"version": "4.2.2",
"description": "1inch Limit Order Protocol",
"repository": {
"type": "git",
Expand Down
71 changes: 48 additions & 23 deletions test/FeeTaker.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ const { buildOrder, buildTakerTraits, signOrder, buildMakerTraits, buildFeeTaker
const { ether } = require('./helpers/utils');

describe('FeeTaker', function () {
let addr, addr1, addr2, addr3;
let addr, addr1, addr2, addr3, addr4;
before(async function () {
[addr, addr1, addr2, addr3] = await ethers.getSigners();
[addr, addr1, addr2, addr3, addr4] = await ethers.getSigners();
});

async function deployContractsAndInit () {
Expand Down Expand Up @@ -100,7 +100,8 @@ describe('FeeTaker', function () {
const takingAmount = ether('0.3');
const integratorFee = BigInt(1e4);
const resolverFee = BigInt(1e3);
const feeRecipient = addr2.address;
const integratorFeeRecipient = addr2.address;
const protocolFeeRecipient = addr3.address;
const whitelist = '0x0a' + addr.address.slice(-20).repeat(10);

const order = buildOrder(
Expand All @@ -114,7 +115,8 @@ describe('FeeTaker', function () {
},
buildFeeTakerExtensions({
feeTaker: await feeTaker.getAddress(),
feeRecipient,
integratorFeeRecipient,
protocolFeeRecipient,
integratorFee,
resolverFee,
whitelist,
Expand All @@ -129,9 +131,15 @@ describe('FeeTaker', function () {
const fillTx = swap.fillOrderArgs(order, r, vs, makingAmount, takerTraits.traits, takerTraits.args);
console.log(`GasUsed: ${(await (await fillTx).wait()).gasUsed.toString()}`);

const feeCalculated = takingAmount * (integratorFee + resolverFee / 2n) / BigInt(1e5);
const integratorFeeCalculated = takingAmount * (integratorFee / 2n) / BigInt(1e5);
const protocolFeeCalculated = takingAmount * (integratorFee / 2n + resolverFee / 2n) / BigInt(1e5);
const totalFeeCalculated = protocolFeeCalculated + integratorFeeCalculated;
await expect(fillTx).to.changeTokenBalances(dai, [addr, addr1], [makingAmount, -makingAmount]);
await expect(fillTx).to.changeTokenBalances(weth, [addr, addr1, feeRecipient], [-takingAmount - feeCalculated, takingAmount, feeCalculated]);
await expect(fillTx).to.changeTokenBalances(
weth,
[addr, addr1, integratorFeeRecipient, protocolFeeRecipient],
[-takingAmount - totalFeeCalculated, takingAmount, integratorFeeCalculated, protocolFeeCalculated],
);
});

it('should charge fee when out of whitelist', async function () {
Expand All @@ -141,7 +149,8 @@ describe('FeeTaker', function () {
const takingAmount = ether('0.3');
const integratorFee = BigInt(1e4);
const resolverFee = BigInt(1e3);
const feeRecipient = addr2.address;
const integratorFeeRecipient = addr2.address;
const protocolFeeRecipient = addr3.address;
const whitelist = '0x0a' + addr2.address.slice(-20).repeat(10);

const order = buildOrder(
Expand All @@ -155,7 +164,8 @@ describe('FeeTaker', function () {
},
buildFeeTakerExtensions({
feeTaker: await feeTaker.getAddress(),
feeRecipient,
integratorFeeRecipient,
protocolFeeRecipient,
integratorFee,
resolverFee,
whitelist,
Expand All @@ -170,11 +180,13 @@ describe('FeeTaker', function () {
const fillTx = swap.fillOrderArgs(order, r, vs, makingAmount, takerTraits.traits, takerTraits.args);
console.log(`GasUsed: ${(await (await fillTx).wait()).gasUsed.toString()}`);

const feeCalculated = takingAmount * (integratorFee + resolverFee) / BigInt(1e5);
const integratorFeeCalculated = takingAmount * (integratorFee / 2n) / BigInt(1e5);
const protocolFeeCalculated = takingAmount * (integratorFee / 2n + resolverFee) / BigInt(1e5);
const totalFeeCalculated = protocolFeeCalculated + integratorFeeCalculated;
await expect(fillTx).to.changeTokenBalances(dai, [addr, addr1], [makingAmount, -makingAmount]);
await expect(fillTx).to.changeTokenBalances(weth,
[addr, addr1, feeRecipient],
[-takingAmount - feeCalculated, takingAmount, feeCalculated],
[addr, addr1, integratorFeeRecipient, protocolFeeRecipient],
[-takingAmount - totalFeeCalculated, takingAmount, integratorFeeCalculated, protocolFeeCalculated],
);
});

Expand All @@ -184,9 +196,9 @@ describe('FeeTaker', function () {
const makingAmount = ether('300');
const takingAmount = ether('0.3');
const integratorFee = BigInt(1e4);
const feeCalculated = takingAmount * integratorFee / BigInt(1e5);
const feeRecipient = addr2.address;
const makerReceiver = addr3.address;
const integratorFeeRecipient = addr2.address;
const protocolFeeRecipient = addr3.address;
const makerReceiver = addr4.address;

const order = buildOrder(
{
Expand All @@ -199,7 +211,8 @@ describe('FeeTaker', function () {
},
buildFeeTakerExtensions({
feeTaker: await feeTaker.getAddress(),
feeRecipient,
integratorFeeRecipient,
protocolFeeRecipient,
makerReceiver,
integratorFee,
}),
Expand All @@ -211,8 +224,16 @@ describe('FeeTaker', function () {
});
const fillTx = swap.fillOrderArgs(order, r, vs, makingAmount, takerTraits.traits, takerTraits.args);
console.log(`GasUsed: ${(await (await fillTx).wait()).gasUsed.toString()}`);

const integratorFeeCalculated = takingAmount * integratorFee / 2n / BigInt(1e5);
const protocolFeeCalculated = takingAmount * integratorFee / 2n / BigInt(1e5);
const totalFeeCalculated = integratorFeeCalculated + protocolFeeCalculated;

await expect(fillTx).to.changeTokenBalances(dai, [addr, addr1], [makingAmount, -makingAmount]);
await expect(fillTx).to.changeTokenBalances(weth, [addr, addr1, feeRecipient, makerReceiver], [-takingAmount - feeCalculated, 0, feeCalculated, takingAmount]);
await expect(fillTx).to.changeTokenBalances(
weth,
[addr, addr1, integratorFeeRecipient, protocolFeeRecipient, makerReceiver],
[-takingAmount - totalFeeCalculated, 0, integratorFeeCalculated, protocolFeeCalculated, takingAmount]);
});

it('should charge fee in eth', async function () {
Expand All @@ -222,7 +243,8 @@ describe('FeeTaker', function () {
const takingAmount = ether('0.3');
const integratorFee = BigInt(1e4);
const feeCalculated = takingAmount * integratorFee / BigInt(1e5);
const feeRecipient = addr2.address;
const integratorFeeRecipient = addr2.address;
const protocolFeeRecipient = addr3.address;

const order = buildOrder(
{
Expand All @@ -236,7 +258,8 @@ describe('FeeTaker', function () {
},
buildFeeTakerExtensions({
feeTaker: await feeTaker.getAddress(),
feeRecipient,
integratorFeeRecipient,
protocolFeeRecipient,
integratorFee,
}),
);
Expand All @@ -249,7 +272,7 @@ describe('FeeTaker', function () {
console.log(`GasUsed: ${(await (await fillTx).wait()).gasUsed.toString()}`);
await expect(fillTx).to.changeTokenBalances(dai, [addr, addr1], [makingAmount, -makingAmount]);
await expect(fillTx).to.changeTokenBalance(weth, addr, -takingAmount - feeCalculated);
await expect(fillTx).to.changeEtherBalances([addr1, feeRecipient], [takingAmount, feeCalculated]);
await expect(fillTx).to.changeEtherBalances([addr1, integratorFeeRecipient, protocolFeeRecipient], [takingAmount, feeCalculated / 2n, feeCalculated / 2n]);
});

it('should charge fee in eth and send the rest to the maker receiver', async function () {
Expand All @@ -259,8 +282,9 @@ describe('FeeTaker', function () {
const takingAmount = ether('0.3');
const integratorFee = BigInt(1e4);
const feeCalculated = takingAmount * integratorFee / BigInt(1e5);
const feeRecipient = addr2.address;
const makerReceiver = addr3.address;
const integratorFeeRecipient = addr2.address;
const protocolFeeRecipient = addr3.address;
const makerReceiver = addr4.address;

const order = buildOrder(
{
Expand All @@ -274,7 +298,8 @@ describe('FeeTaker', function () {
},
buildFeeTakerExtensions({
feeTaker: await feeTaker.getAddress(),
feeRecipient,
integratorFeeRecipient,
protocolFeeRecipient,
makerReceiver,
integratorFee,
}),
Expand All @@ -288,6 +313,6 @@ describe('FeeTaker', function () {
console.log(`GasUsed: ${(await (await fillTx).wait()).gasUsed.toString()}`);
await expect(fillTx).to.changeTokenBalances(dai, [addr, addr1], [makingAmount, -makingAmount]);
await expect(fillTx).to.changeTokenBalance(weth, addr, -takingAmount - feeCalculated);
await expect(fillTx).to.changeEtherBalances([addr1, feeRecipient, makerReceiver], [0, feeCalculated, takingAmount]);
await expect(fillTx).to.changeEtherBalances([addr1, integratorFeeRecipient, protocolFeeRecipient, makerReceiver], [0, feeCalculated / 2n, feeCalculated / 2n, takingAmount]);
});
});
Loading
Loading