Skip to content

Commit

Permalink
Merge pull request #148 from 1inch/feature/separate-gas-fee
Browse files Browse the repository at this point in the history
add separate parameters for gas cost
  • Loading branch information
ZumZoom authored Feb 26, 2024
2 parents f24c2d5 + b6ebb25 commit c925123
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 28 deletions.
34 changes: 23 additions & 11 deletions contracts/extensions/BaseExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ contract BaseExtension is IPreInteraction, IPostInteraction, IAmountGetter {
error OnlyLimitOrderProtocol();

uint256 private constant _BASE_POINTS = 10_000_000; // 100%
uint256 private constant _GAS_PRICE_BASE = 1_000_000; // 1000 means 1 Gwei

address private immutable _LIMIT_ORDER_PROTOCOL;

Expand Down Expand Up @@ -102,10 +103,13 @@ contract BaseExtension is IPreInteraction, IPostInteraction, IAmountGetter {
* piecewise linear function with `N` points. Each point is represented as a pair of
* `(rateBump, timeDelta)`, where `rateBump` is the rate bump in basis points and `timeDelta`
* is the time delta in seconds. The rate bump is interpolated linearly between the points.
* The last point is assumed to be `(0, auctionDuration)`.
* @param auctionDetails AuctionDetails is a tihgtly packed struct of the following format:
* The last point is assumed to be `(0, auctionDuration)`. `gasBumpEstimate` and `gasPriceEstimate`
* are used to estimate the transaction costs which are then offset from the rate bump.
* @param auctionDetails AuctionDetails is a tightly packed struct of the following format:
* ```
* struct AuctionDetails {
* bytes3 gasBumpEstimate;
* bytes4 gasPriceEstimate;
* bytes4 auctionStartTime;
* bytes3 auctionDuration;
* bytes3 initialRateBump;
Expand All @@ -116,29 +120,37 @@ contract BaseExtension is IPreInteraction, IPostInteraction, IAmountGetter {
*/
function _getRateBump(bytes calldata auctionDetails) private view returns (uint256) {
unchecked {
uint256 auctionStartTime = uint32(bytes4(auctionDetails[0:4]));
uint256 auctionFinishTime = auctionStartTime + uint24(bytes3(auctionDetails[4:7]));
uint256 initialRateBump = uint24(bytes3(auctionDetails[7:10]));
uint256 gasBumpEstimate = uint24(bytes3(auctionDetails[0:3]));
uint256 gasPriceEstimate = uint32(bytes4(auctionDetails[3:7]));
uint256 gasBump = gasBumpEstimate == 0 || gasPriceEstimate == 0 ? 0 : gasBumpEstimate * block.basefee / gasPriceEstimate / _GAS_PRICE_BASE;
uint256 auctionStartTime = uint32(bytes4(auctionDetails[7:11]));
uint256 auctionFinishTime = auctionStartTime + uint24(bytes3(auctionDetails[11:14]));
uint256 initialRateBump = uint24(bytes3(auctionDetails[14:17]));
uint256 auctionBump = _getAuctionBump(auctionStartTime, auctionFinishTime, initialRateBump, auctionDetails[17:]);
return auctionBump > gasBump ? auctionBump - gasBump : 0;
}
}

function _getAuctionBump(uint256 auctionStartTime, uint256 auctionFinishTime, uint256 initialRateBump, bytes calldata pointsAndTimeDeltas) private view returns (uint256) {
unchecked {
if (block.timestamp <= auctionStartTime) {
return initialRateBump;
} else if (block.timestamp >= auctionFinishTime) {
return 0; // Means 0% bump
return 0;
}

auctionDetails = auctionDetails[10:];
uint256 currentPointTime = auctionStartTime;
uint256 currentRateBump = initialRateBump;

while (auctionDetails.length > 0) {
uint256 nextRateBump = uint24(bytes3(auctionDetails[:3]));
uint256 nextPointTime = currentPointTime + uint16(bytes2(auctionDetails[3:5]));
while (pointsAndTimeDeltas.length > 0) {
uint256 nextRateBump = uint24(bytes3(pointsAndTimeDeltas[:3]));
uint256 nextPointTime = currentPointTime + uint16(bytes2(pointsAndTimeDeltas[3:5]));
if (block.timestamp <= nextPointTime) {
return ((block.timestamp - currentPointTime) * nextRateBump + (nextPointTime - block.timestamp) * currentRateBump) / (nextPointTime - currentPointTime);
}
currentRateBump = nextRateBump;
currentPointTime = nextPointTime;
auctionDetails = auctionDetails[5:];
pointsAndTimeDeltas = pointsAndTimeDeltas[5:];
}
return (auctionFinishTime - block.timestamp) * currentRateBump / (auctionFinishTime - currentPointTime);
}
Expand Down
34 changes: 34 additions & 0 deletions contracts/mocks/GasBumpChecker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

import { IOrderMixin } from "@1inch/limit-order-protocol-contract/contracts/interfaces/IOrderMixin.sol";
import { BaseExtension } from "../extensions/BaseExtension.sol";

contract GasBumpChecker is BaseExtension {
error InvalidResult(uint256 actual, uint256 expected);

constructor() BaseExtension(address(this)) {}

function testGetTakingAmount(
IOrderMixin.Order calldata order,
bytes calldata extension,
bytes32 orderHash,
address taker,
uint256 makingAmount,
uint256 remainingMakingAmount,
bytes calldata extraData,
uint256 expectedResult
) external payable {
uint256 res = this.getTakingAmount(
order,
extension,
orderHash,
taker,
makingAmount,
remainingMakingAmount,
extraData
);
if (res != expectedResult) revert InvalidResult(res, expectedResult);
}
}
68 changes: 68 additions & 0 deletions test/GasBump.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const { deployContract, time, ether, constants } = require('@1inch/solidity-utils');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { buildOrder, buildMakerTraits } = require('@1inch/limit-order-protocol-contract/test/helpers/orderUtils');
const { initContractsForSettlement } = require('./helpers/fixtures');
const { buildAuctionDetails } = require('./helpers/fusionUtils');
const hre = require('hardhat');
const { network } = hre;

describe('GasBump', function () {
before(async function () {
if (hre.__SOLIDITY_COVERAGE_RUNNING) { this.skip(); }
});

after(async function () {
await network.provider.send('hardhat_setNextBlockBaseFeePerGas', ['0x1']);
});

async function prepare() {
const { contracts: { dai, weth }, accounts: { owner } } = await initContractsForSettlement();
const checker = await deployContract('GasBumpChecker');
const currentTime = (await time.latest()) - time.duration.minutes(1) + 1;
const { details: auctionDetails } = await buildAuctionDetails({
gasBumpEstimate: 10000, // 0.1% of taking amount
gasPriceEstimate: 1000, // 1 gwei
startTime: currentTime,
initialRateBump: 1000000,
points: [[500000, 60]],
});

const order = buildOrder({
maker: owner.address,
makerAsset: await dai.getAddress(),
takerAsset: await weth.getAddress(),
makingAmount: ether('10'),
takingAmount: ether('1'),
makerTraits: buildMakerTraits(),
});

return { order, owner, auctionDetails, checker };
}

async function testGetTakingAmount(checker, order, owner, auctionDetails, basefee, result) {
await network.provider.send('hardhat_setNextBlockBaseFeePerGas', ['0x' + basefee.toString(16)]);
await checker.testGetTakingAmount(
order, '0x', constants.ZERO_BYTES32, owner.address, ether('10'), ether('10'), auctionDetails, result, { gasPrice: basefee },
);
}

it('0 gwei = no gas fee', async function () {
const { order, owner, auctionDetails, checker } = await loadFixture(prepare);
await testGetTakingAmount(checker, order, owner, auctionDetails, 0, ether('1.05'));
});

it('0.1 gwei = 0.01% gas fee', async function () {
const { order, owner, auctionDetails, checker } = await loadFixture(prepare);
await testGetTakingAmount(checker, order, owner, auctionDetails, 1e8, ether('1.0499'));
});

it('15 gwei = 1.5% gas fee', async function () {
const { order, owner, auctionDetails, checker } = await loadFixture(prepare);
await testGetTakingAmount(checker, order, owner, auctionDetails, 15e9, ether('1.035'));
});

it('100 gwei = 10% gas fee, should be capped with takingAmount', async function () {
const { order, owner, auctionDetails, checker } = await loadFixture(prepare);
await testGetTakingAmount(checker, order, owner, auctionDetails, 100e9, ether('1'));
});
});
12 changes: 3 additions & 9 deletions test/MeasureGas.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,7 @@ describe('MeasureGas', function () {
const { contracts: { dai, weth, lopv4, settlementExtension }, accounts: { owner, alice }, others: { chainId } } = await loadFixture(initContractsAndApproves);

const auctionStartTime = await time.latest();
const auctionDetails = ethers.solidityPacked(
['uint32', 'uint24', 'uint24'], [auctionStartTime, time.duration.hours(1), 0],
);
const { details: auctionDetails } = await buildAuctionDetails({ startTime: auctionStartTime, duration: time.duration.hours(1) });

const order = buildOrder({
maker: alice.address,
Expand Down Expand Up @@ -338,9 +336,7 @@ describe('MeasureGas', function () {
const { contracts: { dai, weth, lopv4, settlementExtension, resolver }, accounts: { alice }, others: { chainId } } = await loadFixture(initContractsAndApproves);

const auctionStartTime = await time.latest();
const auctionDetails = ethers.solidityPacked(
['uint32', 'uint24', 'uint24'], [auctionStartTime, time.duration.hours(1), 0],
);
const { details: auctionDetails } = await buildAuctionDetails({ startTime: auctionStartTime, duration: time.duration.hours(1) });

const order = buildOrder({
maker: alice.address,
Expand Down Expand Up @@ -386,9 +382,7 @@ describe('MeasureGas', function () {
const { contracts: { dai, weth, lopv4, settlementExtension, resolver }, accounts: { owner, alice }, others: { chainId, abiCoder } } = await loadFixture(initContractsAndApproves);

const auctionStartTime = await time.latest();
const auctionDetails = ethers.solidityPacked(
['uint32', 'uint24', 'uint24'], [auctionStartTime, time.duration.hours(1), 0],
);
const { details: auctionDetails } = await buildAuctionDetails({ startTime: auctionStartTime, duration: time.duration.hours(1) });

const resolverArgs = abiCoder.encode(
['address[]', 'bytes[]'],
Expand Down
8 changes: 2 additions & 6 deletions test/WhitelistChecker.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,8 @@ describe('WhitelistChecker', function () {
isInnermostOrder: true,
});

// Change resolver to fakeResolver in takerInteraction
const fakeFillOrderToData = fillOrderToData.slice(0, fillOrderToData.length - 86) + fakeResolver.target.substring(2) + fillOrderToData.slice(-46);

await expect(resolver.settleOrders(fakeFillOrderToData)).to.be.revertedWithCustomError(
resolver, 'NotTaker',
);
// try to make txn from fakeResolver
await expect(fakeResolver.settleOrders(fillOrderToData)).to.be.revertedWithCustomError(resolver, 'NotTaker');
});

it('only LOP can use takerInteraction method', async function () {
Expand Down
6 changes: 4 additions & 2 deletions test/helpers/fusionUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ async function buildCalldataForOrder({
}

async function buildAuctionDetails({
gasBumpEstimate = 0,
gasPriceEstimate = 0,
startTime, // default is time.latest()
duration = 1800, // default is 30 minutes
delay = 0,
Expand All @@ -91,12 +93,12 @@ async function buildAuctionDetails({
} = {}) {
startTime = startTime || await time.latest();
let details = ethers.solidityPacked(
['uint32', 'uint24', 'uint24'], [startTime + delay, duration, initialRateBump],
['uint24', 'uint32', 'uint32', 'uint24', 'uint24'], [gasBumpEstimate, gasPriceEstimate, startTime + delay, duration, initialRateBump],
);
for (let i = 0; i < points.length; i++) {
details += trim0x(ethers.solidityPacked(['uint24', 'uint16'], [points[i][0], points[i][1]]));
}
return { startTime, details, delay, duration, initialRateBump };
return { gasBumpEstimate, gasPriceEstimate, startTime, duration, delay, initialRateBump, details };
}

module.exports = {
Expand Down

0 comments on commit c925123

Please sign in to comment.