Skip to content

Commit c3ef880

Browse files
authored
Merge pull request #341 from consenlabs/refactor/gas-opt-techniques
optimize gas usage with common techniques and refactor code structure
2 parents b01d556 + da74666 commit c3ef880

38 files changed

+301
-322
lines changed

.github/workflows/gas-diff.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
node-version: [16.x]
1818

1919
steps:
20-
- uses: actions/checkout@v3
20+
- uses: actions/checkout@v4
2121
with:
2222
submodules: recursive
2323

@@ -47,14 +47,14 @@ jobs:
4747
cat gasreport-fork.ansi >> $GITHUB_STEP_SUMMARY
4848
4949
- name: Compare gas reports of local tests
50-
uses: Rubilmax/foundry-gas-diff@v3.14
50+
uses: Rubilmax/foundry-gas-diff@v3
5151
with:
5252
report: gasreport-local.ansi
5353
ignore: test/**/*
5454
id: gas_diff_local
5555

5656
- name: Compare gas reports of fork tests
57-
uses: Rubilmax/foundry-gas-diff@v3.14
57+
uses: Rubilmax/foundry-gas-diff@v3
5858
with:
5959
report: gasreport-fork.ansi
6060
ignore: test/**/*

.github/workflows/tokenlon-contracts.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
node-version: [16.x]
1515

1616
steps:
17-
- uses: actions/checkout@v2
17+
- uses: actions/checkout@v4
1818
with:
1919
submodules: recursive
2020
- name: Use Node.js ${{ matrix.node-version }}

contracts/GenericSwap.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ contract GenericSwap is IGenericSwap, TokenCollector, EIP712 {
6666
) private returns (uint256 returnAmount) {
6767
if (_swapData.expiry < block.timestamp) revert ExpiredOrder();
6868
if (_swapData.recipient == address(0)) revert ZeroAddress();
69+
if (_swapData.takerTokenAmount == 0) revert SwapWithZeroAmount();
6970

7071
address _inputToken = _swapData.takerToken;
7172
address _outputToken = _swapData.makerToken;
@@ -77,7 +78,7 @@ contract GenericSwap is IGenericSwap, TokenCollector, EIP712 {
7778
_collect(_inputToken, _authorizedUser, _swapData.maker, _swapData.takerTokenAmount, _takerTokenPermit);
7879
}
7980

80-
IStrategy(_swapData.maker).executeStrategy{ value: msg.value }(_inputToken, _outputToken, _swapData.takerTokenAmount, _swapData.strategyData);
81+
IStrategy(_swapData.maker).executeStrategy{ value: msg.value }(_outputToken, _swapData.strategyData);
8182

8283
returnAmount = _outputToken.getBalance(address(this));
8384
if (returnAmount > 1) {

contracts/LimitOrderSwap.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ contract LimitOrderSwap is ILimitOrderSwap, Ownable, TokenCollector, EIP712, Ree
193193
(address strategy, bytes memory strategyData) = abi.decode(takerParams.extraAction, (address, bytes));
194194
// the coverage report indicates that the following line causes the if statement to not be fully covered,
195195
// even if the logic of the executeStrategy function is empty, this if statement is still not covered.
196-
IStrategy(strategy).executeStrategy(order.makerToken, order.takerToken, makerSpendingAmount - fee, strategyData);
196+
IStrategy(strategy).executeStrategy(order.takerToken, strategyData);
197197
}
198198

199199
// taker -> maker

contracts/SmartOrderStrategy.sol

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,32 +37,20 @@ contract SmartOrderStrategy is ISmartOrderStrategy, AdminManagement {
3737
}
3838

3939
/// @inheritdoc IStrategy
40-
function executeStrategy(address inputToken, address outputToken, uint256 inputAmount, bytes calldata data) external payable onlyGenericSwap {
41-
if (inputAmount == 0) revert ZeroInput();
42-
43-
Operation[] memory ops = abi.decode(data, (Operation[]));
40+
function executeStrategy(address targetToken, bytes calldata strategyData) external payable onlyGenericSwap {
41+
Operation[] memory ops = abi.decode(strategyData, (Operation[]));
4442
if (ops.length == 0) revert EmptyOps();
4543

46-
// wrap ETH to WETH if inputToken is ETH
47-
if (Asset.isETH(inputToken)) {
48-
if (msg.value != inputAmount) revert InvalidMsgValue();
49-
// the coverage report indicates that the following line causes this branch to not be covered by our tests
50-
// even though we tried all possible success and revert scenarios
51-
IWETH(weth).deposit{ value: inputAmount }();
52-
} else {
53-
if (msg.value != 0) revert InvalidMsgValue();
54-
}
55-
5644
uint256 opsCount = ops.length;
5745
for (uint256 i; i < opsCount; ++i) {
5846
Operation memory op = ops[i];
5947
_call(op.dest, op.inputToken, op.ratioNumerator, op.ratioDenominator, op.dataOffset, op.value, op.data);
6048
}
6149

62-
// unwrap WETH to ETH if outputToken is ETH
63-
if (Asset.isETH(outputToken)) {
50+
// unwrap WETH to ETH if targetToken is ETH
51+
if (Asset.isETH(targetToken)) {
6452
// the if statement is not fully covered by the tests even replacing `makerToken.isETH()` with `makerToken == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`
65-
// and crafting some cases where outputToken is ETH and non-ETH
53+
// and crafting some cases where targetToken is ETH and non-ETH
6654
uint256 wethBalance = IWETH(weth).balanceOf(address(this));
6755

6856
if (wethBalance > 0) {
@@ -71,15 +59,15 @@ contract SmartOrderStrategy is ISmartOrderStrategy, AdminManagement {
7159
}
7260
}
7361

74-
uint256 selfBalance = Asset.getBalance(outputToken, address(this));
62+
uint256 selfBalance = Asset.getBalance(targetToken, address(this));
7563
if (selfBalance > 1) {
7664
unchecked {
7765
--selfBalance;
7866
}
7967
}
8068

8169
// transfer output tokens back to the generic swap contract
82-
Asset.transferTo(outputToken, payable(genericSwap), selfBalance);
70+
Asset.transferTo(targetToken, payable(genericSwap), selfBalance);
8371
}
8472

8573
/// @dev This function adjusts the input amount based on a ratio if specified, then calls the destination contract with data.
@@ -102,12 +90,6 @@ contract SmartOrderStrategy is ISmartOrderStrategy, AdminManagement {
10290
// adjust amount if ratio != 0
10391
if (_ratioNumerator != 0) {
10492
uint256 inputTokenBalance = IERC20(_inputToken).balanceOf(address(this));
105-
// leaving one wei for gas optimization
106-
if (inputTokenBalance > 1) {
107-
unchecked {
108-
--inputTokenBalance;
109-
}
110-
}
11193

11294
// calculate input amount if ratio should be applied
11395
if (_ratioNumerator != _ratioDenominator) {

contracts/abstracts/EIP712.sol

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ pragma solidity ^0.8.0;
66
/// @notice This contract implements the EIP-712 standard for structured data hashing and signing.
77
/// @dev This contract provides functions to handle EIP-712 domain separator and hash calculation.
88
abstract contract EIP712 {
9-
// EIP-191 Header
10-
string public constant EIP191_HEADER = "\x19\x01";
11-
129
// EIP-712 Domain
1310
string public constant EIP712_NAME = "Tokenlon";
1411
string public constant EIP712_VERSION = "v6";
@@ -43,9 +40,24 @@ abstract contract EIP712 {
4340

4441
/// @notice Calculate the EIP712 hash of a structured data hash.
4542
/// @param structHash The hash of the structured data.
46-
/// @return The EIP712 hash of the structured data.
47-
function getEIP712Hash(bytes32 structHash) internal view returns (bytes32) {
48-
return keccak256(abi.encodePacked(EIP191_HEADER, _getDomainSeparator(), structHash));
43+
/// @return digest The EIP712 hash of the structured data.
44+
function getEIP712Hash(bytes32 structHash) internal view returns (bytes32 digest) {
45+
// return keccak256(abi.encodePacked("\x19\x01", _getDomainSeparator(), structHash));
46+
47+
digest = _getDomainSeparator();
48+
49+
// reference:
50+
// 1. solady: https://github.com/Vectorized/solady/blob/main/src/utils/EIP712.sol#L138-L147
51+
// 2. 1inch: https://etherscan.io/address/0x111111125421cA6dc452d289314280a0f8842A65#code (line 1204~1209)
52+
// solhint-disable no-inline-assembly
53+
assembly {
54+
// Compute the digest.
55+
mstore(0x00, 0x1901000000000000000000000000000000000000000000000000000000000000) // Store "\x19\x01".
56+
mstore(0x02, digest) // Store the domain separator.
57+
mstore(0x22, structHash) // Store the struct hash.
58+
digest := keccak256(0x0, 0x42)
59+
mstore(0x22, 0) // Restore the part of the free memory slot that was overwritten.
60+
}
4961
}
5062

5163
/// @notice Get the current EIP712 domain separator.

contracts/abstracts/Ownable.sol

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ abstract contract Ownable {
99
address public owner;
1010
address public nominatedOwner;
1111

12+
/// @notice Event emitted when a new owner is nominated.
13+
/// @param newOwner The address of the new nominated owner.
14+
event OwnerNominated(address indexed newOwner);
15+
16+
/// @notice Event emitted when ownership is transferred.
17+
/// @param oldOwner The address of the previous owner.
18+
/// @param newOwner The address of the new owner.
19+
event OwnerChanged(address indexed oldOwner, address indexed newOwner);
20+
1221
/// @notice Error to be thrown when the caller is not the owner.
1322
/// @dev This error is used to ensure that only the owner can call certain functions.
1423
error NotOwner();
@@ -25,14 +34,10 @@ abstract contract Ownable {
2534
/// @dev This error is used to prevent nominating a new owner when one is already nominated.
2635
error NominationExists();
2736

28-
/// @notice Event emitted when a new owner is nominated.
29-
/// @param newOwner The address of the new nominated owner.
30-
event OwnerNominated(address indexed newOwner);
31-
32-
/// @notice Event emitted when ownership is transferred.
33-
/// @param oldOwner The address of the previous owner.
34-
/// @param newOwner The address of the new owner.
35-
event OwnerChanged(address indexed oldOwner, address indexed newOwner);
37+
modifier onlyOwner() {
38+
if (msg.sender != owner) revert NotOwner();
39+
_;
40+
}
3641

3742
/// @notice Constructor to set the initial owner of the contract.
3843
/// @param _owner The address of the initial owner.
@@ -41,11 +46,6 @@ abstract contract Ownable {
4146
owner = _owner;
4247
}
4348

44-
modifier onlyOwner() {
45-
if (msg.sender != owner) revert NotOwner();
46-
_;
47-
}
48-
4949
/// @notice Accept the ownership transfer.
5050
/// @dev Only the nominated owner can call this function to accept the ownership.
5151
function acceptOwnership() external {

contracts/abstracts/TokenCollector.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ import { IUniswapPermit2 } from "../interfaces/IUniswapPermit2.sol";
1515
abstract contract TokenCollector {
1616
using SafeERC20 for IERC20;
1717

18-
/// @notice Error to be thrown when Permit2 data is empty.
19-
/// @dev This error is used to ensure Permit2 data is provided when required.
20-
error Permit2DataEmpty();
21-
2218
/// @title Token Collection Sources
2319
/// @notice Enumeration of possible token collection sources.
2420
/// @dev Represents the various methods for collecting tokens.
@@ -33,6 +29,10 @@ abstract contract TokenCollector {
3329
address public immutable permit2;
3430
address public immutable allowanceTarget;
3531

32+
/// @notice Error to be thrown when Permit2 data is empty.
33+
/// @dev This error is used to ensure Permit2 data is provided when required.
34+
error Permit2DataEmpty();
35+
3636
/// @notice Constructor to set the Permit2 and allowance target addresses.
3737
/// @param _permit2 The address of the Uniswap Permit2 contract.
3838
/// @param _allowanceTarget The address of the allowance target contract.

contracts/interfaces/IGenericSwap.sol

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,28 @@ import { GenericSwapData } from "../libraries/GenericSwapData.sol";
88
/// @notice Interface for a generic swap contract.
99
/// @dev This interface defines functions and events related to executing swaps and handling swap errors.
1010
interface IGenericSwap {
11+
/// @notice Event emitted when a swap is executed.
12+
/// @param swapHash The hash of the swap data.
13+
/// @param maker The address of the maker initiating the swap.
14+
/// @param taker The address of the taker executing the swap.
15+
/// @param recipient The address receiving the output tokens.
16+
/// @param inputToken The address of the input token.
17+
/// @param inputAmount The amount of input tokens.
18+
/// @param outputToken The address of the output token.
19+
/// @param outputAmount The amount of output tokens received.
20+
/// @param salt The salt value used in the swap.
21+
event Swap(
22+
bytes32 indexed swapHash,
23+
address indexed maker,
24+
address indexed taker,
25+
address recipient,
26+
address inputToken,
27+
uint256 inputAmount,
28+
address outputToken,
29+
uint256 outputAmount,
30+
uint256 salt
31+
);
32+
1133
/// @notice Error to be thrown when a swap is already filled.
1234
/// @dev This error is used when attempting to fill a swap that has already been completed.
1335
error AlreadyFilled();
@@ -32,27 +54,9 @@ interface IGenericSwap {
3254
/// @dev This error is used to ensure that a valid address is provided.
3355
error ZeroAddress();
3456

35-
/// @notice Event emitted when a swap is executed.
36-
/// @param swapHash The hash of the swap data.
37-
/// @param maker The address of the maker initiating the swap.
38-
/// @param taker The address of the taker executing the swap.
39-
/// @param recipient The address receiving the output tokens.
40-
/// @param inputToken The address of the input token.
41-
/// @param inputAmount The amount of input tokens.
42-
/// @param outputToken The address of the output token.
43-
/// @param outputAmount The amount of output tokens received.
44-
/// @param salt The salt value used in the swap.
45-
event Swap(
46-
bytes32 indexed swapHash,
47-
address indexed maker,
48-
address indexed taker,
49-
address recipient,
50-
address inputToken,
51-
uint256 inputAmount,
52-
address outputToken,
53-
uint256 outputAmount,
54-
uint256 salt
55-
);
57+
/// @notice Error to be thrown when a swap amount is zero.
58+
/// @dev This error is used to ensure that a valid, nonzero swap amount is provided.
59+
error SwapWithZeroAmount();
5660

5761
/// @notice Executes a swap using provided swap data and taker token permit.
5862
/// @param swapData The swap data containing details of the swap.

contracts/interfaces/ILimitOrderSwap.sol

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,47 @@ import { LimitOrder } from "../libraries/LimitOrder.sol";
88
/// @notice Interface for a limit order swap contract.
99
/// @dev This interface defines functions and events related to executing and managing limit orders.
1010
interface ILimitOrderSwap {
11+
/// @notice Struct containing parameters for the taker.
12+
/// @dev This struct encapsulates the parameters necessary for a taker to fill a limit order.
13+
struct TakerParams {
14+
uint256 takerTokenAmount; // Amount of tokens taken by the taker.
15+
uint256 makerTokenAmount; // Amount of tokens provided by the maker.
16+
address recipient; // Address to receive the tokens.
17+
bytes extraAction; // Additional action to be performed.
18+
bytes takerTokenPermit; // Permit for spending taker's tokens.
19+
}
20+
21+
/// @notice Emitted when the fee collector address is updated.
22+
/// @param newFeeCollector The address of the new fee collector.
23+
event SetFeeCollector(address newFeeCollector);
24+
25+
/// @notice Emitted when a limit order is successfully filled.
26+
/// @param orderHash The hash of the limit order.
27+
/// @param taker The address of the taker filling the order.
28+
/// @param maker The address of the maker who created the order.
29+
/// @param takerToken The address of the token taken by the taker.
30+
/// @param takerTokenFilledAmount The amount of taker tokens filled.
31+
/// @param makerToken The address of the token received by the maker.
32+
/// @param makerTokenSettleAmount The amount of maker tokens settled.
33+
/// @param fee The fee amount paid for the order.
34+
/// @param recipient The address receiving the tokens.
35+
event LimitOrderFilled(
36+
bytes32 indexed orderHash,
37+
address indexed taker,
38+
address indexed maker,
39+
address takerToken,
40+
uint256 takerTokenFilledAmount,
41+
address makerToken,
42+
uint256 makerTokenSettleAmount,
43+
uint256 fee,
44+
address recipient
45+
);
46+
47+
/// @notice Emitted when an order is canceled.
48+
/// @param orderHash The hash of the canceled order.
49+
/// @param maker The address of the maker who canceled the order.
50+
event OrderCanceled(bytes32 orderHash, address maker);
51+
1152
/// @notice Error to be thrown when an order has expired.
1253
/// @dev Thrown when attempting to fill an order that has already expired.
1354
error ExpiredOrder();
@@ -68,47 +109,6 @@ interface ILimitOrderSwap {
68109
/// @dev Thrown when an operation is attempted by an unauthorized caller who is not the maker of the order.
69110
error NotOrderMaker();
70111

71-
/// @notice Emitted when the fee collector address is updated.
72-
/// @param newFeeCollector The address of the new fee collector.
73-
event SetFeeCollector(address newFeeCollector);
74-
75-
/// @notice Emitted when a limit order is successfully filled.
76-
/// @param orderHash The hash of the limit order.
77-
/// @param taker The address of the taker filling the order.
78-
/// @param maker The address of the maker who created the order.
79-
/// @param takerToken The address of the token taken by the taker.
80-
/// @param takerTokenFilledAmount The amount of taker tokens filled.
81-
/// @param makerToken The address of the token received by the maker.
82-
/// @param makerTokenSettleAmount The amount of maker tokens settled.
83-
/// @param fee The fee amount paid for the order.
84-
/// @param recipient The address receiving the tokens.
85-
event LimitOrderFilled(
86-
bytes32 indexed orderHash,
87-
address indexed taker,
88-
address indexed maker,
89-
address takerToken,
90-
uint256 takerTokenFilledAmount,
91-
address makerToken,
92-
uint256 makerTokenSettleAmount,
93-
uint256 fee,
94-
address recipient
95-
);
96-
97-
/// @notice Emitted when an order is canceled.
98-
/// @param orderHash The hash of the canceled order.
99-
/// @param maker The address of the maker who canceled the order.
100-
event OrderCanceled(bytes32 orderHash, address maker);
101-
102-
/// @notice Struct containing parameters for the taker.
103-
/// @dev This struct encapsulates the parameters necessary for a taker to fill a limit order.
104-
struct TakerParams {
105-
uint256 takerTokenAmount; // Amount of tokens taken by the taker.
106-
uint256 makerTokenAmount; // Amount of tokens provided by the maker.
107-
address recipient; // Address to receive the tokens.
108-
bytes extraAction; // Additional action to be performed.
109-
bytes takerTokenPermit; // Permit for spending taker's tokens.
110-
}
111-
112112
/// @notice Fills a limit order.
113113
/// @param order The limit order to be filled.
114114
/// @param makerSignature The signature of the maker authorizing the fill.

0 commit comments

Comments
 (0)