Skip to content

Conversation

@alex0207s
Copy link
Contributor

@alex0207s alex0207s commented Feb 3, 2025

  • Enable the via-ir setting.
  • Increase optimizer_runs from 1,000 to 65,536.
  • Remove unused function parameters in the executeStrategy function.
  • Remove the logic that leaves a one-wei gap between operations in the SmartOrderStrategy contract, as it introduces unnecessary overhead.
  • Rewrite the getEIP712Hash function of EIP-712 using assembly code.
  • Refactor some interfaces to comply with linter rules.
  • Upgrade CI packages.

@github-actions
Copy link

github-actions bot commented Feb 3, 2025

Changes to gas cost

Generated at commit: 602387b586382295873ca62e3691488e02414418, compared to commit: b01d556be855a7ca334957b0e62ed27aa38a7b7e

🧾 Summary (20% most significant diffs)

Contract Method Avg (+/-) %
AllowanceTarget spendFromUserTo
unpause
-1,176 ✅
-183 ✅
-2.29%
-0.72%

Full diff report 👇
Contract Deployment Cost (+/-) Method Min (+/-) % Avg (+/-) % Median (+/-) % Max (+/-) % # Calls (+/-)
AllowanceTarget 643,922 (+38,965) pause
spendFromUserTo
unpause
23,485 (+65)
24,715 (-310)
23,375 (-46)
+0.28%
-1.24%
-0.20%
26,545 (-60)
50,069 (-1,176)
25,350 (-183)
-0.23%
-2.29%
-0.72%
27,565 (-102)
48,497 (-1,330)
25,350 (-183)
-0.37%
-2.67%
-0.72%
27,565 (-102)
89,862 (-1,714)
27,326 (-319)
-0.37%
-1.87%
-1.15%
4 (0)
8 (0)
2 (0)

@github-actions
Copy link

github-actions bot commented Feb 3, 2025

Changes to gas cost

Generated at commit: 602387b586382295873ca62e3691488e02414418, compared to commit: b01d556be855a7ca334957b0e62ed27aa38a7b7e

🧾 Summary (20% most significant diffs)

Contract Method Avg (+/-) %
RFQ feeCollector
owner
+2,212 ❌
+2,101 ❌
+580.58%
+521.34%
LimitOrderSwap feeCollector
isOrderCanceled
orderHashToMakerTokenFilledAmount
owner
+2,256 ❌
+2,234 ❌
+1,926 ❌
+2,123 ❌
+559.80%
+443.25%
+409.79%
+526.80%
CoordinatedTaker EIP712_DOMAIN_SEPARATOR
coordinator
owner
+314 ❌
+1,891 ❌
+2,179 ❌
+101.62%
+479.95%
+527.60%

Full diff report 👇
Contract Deployment Cost (+/-) Method Min (+/-) % Avg (+/-) % Median (+/-) % Max (+/-) % # Calls (+/-)
RFQ 2,628,391 (+208,452) allowanceTarget
cancelRFQOffer
feeCollector
fillRFQ
fillRFQWithSig
owner
permit2
setFeeCollector
weth
424 (+164)
24,027 (+34)
2,593 (+2,212)
31,518 (-291)
33,812 (-53)
2,504 (+2,101)
182 (-102)
23,854 (+64)
270 (+9)
+63.08%
+0.14%
+580.58%
-0.91%
-0.16%
+521.34%
-35.92%
+0.27%
+3.45%
424 (+164)
35,906 (-19)
2,593 (+2,212)
128,429 (-1,873)
111,785 (-1,579)
2,504 (+2,101)
182 (-102)
26,019 (+58)
270 (+9)
+63.08%
-0.05%
+580.58%
-1.44%
-1.39%
+521.34%
-35.92%
+0.22%
+3.45%
424 (+164)
27,978 (+9)
2,593 (+2,212)
145,801 (-2,081)
76,771 (-866)
2,504 (+2,101)
182 (-102)
24,069 (+74)
270 (+9)
+63.08%
+0.03%
+580.58%
-1.41%
-1.12%
+521.34%
-35.92%
+0.31%
+3.45%
424 (+164)
49,775 (-73)
2,593 (+2,212)
236,417 (-2,380)
224,772 (-3,820)
2,504 (+2,101)
182 (-102)
30,134 (+35)
270 (+9)
+63.08%
-0.15%
+580.58%
-1.00%
-1.67%
+521.34%
-35.92%
+0.12%
+3.45%
1 (0)
5 (0)
2 (0)
24 (0)
3 (0)
1 (0)
1 (0)
3 (0)
1 (0)
LimitOrderSwap 2,697,322 (+17,207) allowanceTarget
cancelOrder
feeCollector
fillLimitOrder
fillLimitOrderFullOrKill
fillLimitOrderGroup
isOrderCanceled
orderHashToMakerTokenFilledAmount
owner
permit2
setFeeCollector
weth
446 (+141)
29,491 (+237)
2,659 (+2,256)
35,980 (-104)
47,919 (-345)
31,149 (-10)
2,738 (+2,234)
2,396 (+1,926)
2,526 (+2,123)
182 (-102)
23,898 (+108)
292 (+9)
+46.23%
+0.81%
+559.80%
-0.29%
-0.71%
-0.03%
+443.25%
+409.79%
+526.80%
-35.92%
+0.45%
+3.18%
446 (+141)
40,897 (+233)
2,659 (+2,256)
1,177,683 (-1,721)
102,721 (-1,271)
172,112 (-2,492)
2,738 (+2,234)
2,396 (+1,926)
2,526 (+2,123)
182 (-102)
26,063 (+102)
292 (+9)
+46.23%
+0.57%
+559.80%
-0.15%
-1.22%
-1.43%
+443.25%
+409.79%
+526.80%
-35.92%
+0.39%
+3.18%
446 (+141)
34,161 (+311)
2,659 (+2,256)
158,698 (-2,970)
47,919 (-345)
195,696 (-2,421)
2,738 (+2,234)
2,396 (+1,926)
2,526 (+2,123)
182 (-102)
24,113 (+118)
292 (+9)
+46.23%
+0.92%
+559.80%
-1.84%
-0.71%
-1.22%
+443.25%
+409.79%
+526.80%
-35.92%
+0.49%
+3.18%
446 (+141)
52,957 (+204)
2,659 (+2,256)
29,534,754 (-123)
212,327 (-3,122)
287,118 (-4,631)
2,738 (+2,234)
2,396 (+1,926)
2,526 (+2,123)
182 (-102)
30,178 (+79)
292 (+9)
+46.23%
+0.39%
+559.80%
-0.00%
-1.45%
-1.59%
+443.25%
+409.79%
+526.80%
-35.92%
+0.26%
+3.18%
8 (0)
7 (0)
8 (0)
28 (0)
3 (0)
9 (0)
1 (0)
1 (0)
8 (0)
8 (0)
3 (0)
8 (0)
CoordinatedTaker 2,404,834 (+84,175) EIP712_DOMAIN_SEPARATOR
allowanceTarget
approveTokens
coordinator
limitOrderSwap
owner
permit2
setCoordinator
submitLimitOrderFill
weth
623 (+314)
534 (+263)
26,822 (-267)
2,285 (+1,891)
556 (+263)
2,592 (+2,179)
226 (-46)
23,898 (+158)
32,910 (-170)
314 (+20)
+101.62%
+97.05%
-0.99%
+479.95%
+89.76%
+527.60%
-16.91%
+0.67%
-0.51%
+6.80%
623 (+314)
534 (+263)
166,962 (-2,228)
2,285 (+1,891)
556 (+263)
2,592 (+2,179)
226 (-46)
26,055 (+148)
126,645 (-1,729)
314 (+20)
+101.62%
+97.05%
-1.32%
+479.95%
+89.76%
+527.60%
-16.91%
+0.57%
-1.35%
+6.80%
623 (+314)
534 (+263)
186,496 (-2,462)
2,285 (+1,891)
556 (+263)
2,592 (+2,179)
226 (-46)
24,101 (+162)
69,687 (-333)
314 (+20)
+101.62%
+97.05%
-1.30%
+479.95%
+89.76%
+527.60%
-16.91%
+0.68%
-0.48%
+6.80%
623 (+314)
534 (+263)
186,496 (-2,462)
2,285 (+1,891)
556 (+263)
2,592 (+2,179)
226 (-46)
30,166 (+123)
252,405 (-4,068)
314 (+20)
+101.62%
+97.05%
-1.30%
+479.95%
+89.76%
+527.60%
-16.91%
+0.41%
-1.59%
+6.80%
2 (0)
1 (0)
15 (0)
2 (0)
1 (0)
1 (0)
1 (0)
3 (0)
7 (0)
1 (0)
GenericSwap 1,726,929 (+5,457) allowanceTarget
executeSwap
executeSwapWithSig
permit2
314 (+54)
32,036 (-277)
39,061 (-446)
182 (-79)
+20.77%
-0.86%
-1.13%
-30.27%
314 (+54)
109,248 (-8,370)
165,967 (-2,387)
182 (-79)
+20.77%
-7.12%
-1.42%
-30.27%
314 (+54)
97,813 (-17,498)
172,667 (-2,409)
182 (-79)
+20.77%
-15.17%
-1.38%
-30.27%
314 (+54)
248,141 (-3,768)
279,472 (-4,284)
182 (-79)
+20.77%
-1.50%
-1.51%
-30.27%
1 (0)
13 (+1)
4 (0)
1 (0)
SmartOrderStrategy 1,163,069 (-36,334) approveTokens
executeStrategy
55,489 (-773)
22,468 (-758)
-1.37%
-3.26%
173,320 (-11,682)
144,361 (+22,115)
-6.31%
+18.09%
264,958 (-3,717)
148,816 (+58,046)
-1.38%
+63.95%
264,970 (-3,717)
607,558 (-2,615)
-1.38%
-0.43%
32 (-1)
14 (-4)

@alex0207s alex0207s requested a review from NIC619 February 4, 2025 08:15
assembly {
// Compute the digest.
mstore(0x00, 0x1901000000000000000000000000000000000000000000000000000000000000) // Store "\x19\x01".
mstore(0x2, digest) // Store the domain separator.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe align with other hex values:

Suggested change
mstore(0x2, digest) // Store the domain separator.
mstore(0x02, digest) // Store the domain separator.

digest = _getDomainSeparator();

// solhint-disable no-inline-assembly
assembly {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those inline assemblies copied from some standard library? How much gas does it save?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use OZ's 712 implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those inline assemblies copied from some standard library?

The assembly code was referenced from soladay's implementaion and 1inch's implementation (line 1204~1209)

How much gas does it save?

Basically, it saves 135 gas when testing the getEIP712Hash function. However, the main value of using assembly here is that it helps avoid quadratic memory expansion costs, as we reuse the same memory slot for hashing.

reference: https://x.com/optimizoor/status/1825913380244435408?mx=2

Is it possible to use OZ's 712 implementation?

Our implementation declares some variables as public, while the OZ's implementation declares them as private. I'm not sure whether this difference might cause issues in the front-end or back-end. I will switch to OZ's (or slady) implementation after verifying that it does not break anything on either end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a comment referencing the code copied from

assertFalse(SignatureValidator.validateSignature(vm.addr(walletAdminPrivateKey), otherDigest, signature));
}

/// forge-config: default.allow_internal_expect_revert = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the setting for? Where is it checked?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reason as mentioned above.

Add this config setting to resolve the following error:
[FAIL: call didn't revert at a lower depth than cheatcode call depth]

see this: https://book.getfoundry.sh/announcements#-notes

{
bytes memory makerSpecificData = abi.encode(defaultAMMPath);
bytes memory strategyData = abi.encode(UNISWAP_SWAP_ROUTER_02_ADDRESS, makerSpecificData);
bytes memory strategyData = abi.encode(UNISWAP_SWAP_ROUTER_02_ADDRESS, order.makerToken, order.makerTokenAmount - fee, makerSpecificData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why added these two parameters? Is it because the change of executeStrategy interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the strategyData here is for the mockLimitOrderTaker, which inherits an interface that defines the executeStrategy function. To minimize breaking changes for the mock contract, we simply encode these two parameters in strategyData.

@NIC619
Copy link
Contributor

NIC619 commented Feb 5, 2025

Can you still run forge coverage after enabled via-ir?

@alex0207s
Copy link
Contributor Author

alex0207s commented Feb 6, 2025

Can you still run forge coverage after enabled via-ir?

Yes, it works fine after enabling the via-ir setting.

if (ops.length == 0) revert EmptyOps();

// wrap ETH to WETH if inputToken is ETH
if (Asset.isETH(inputToken)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if statement duplicates the logic in the GenericSwap contract: https://github.com/consenlabs/tokenlon-contracts/blob/v6.0.1/contracts/GenericSwap.sol#L68-L75

if (msg.value != inputAmount) revert InvalidMsgValue();
// the coverage report indicates that the following line causes this branch to not be covered by our tests
// even though we tried all possible success and revert scenarios
IWETH(weth).deposit{ value: inputAmount }();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't wrap ETH directly, as it could prevent SmartOrderStrategy from interacting with functions that require ETH.

/// @dev This contract provides functions to handle EIP-712 domain separator and hash calculation.
abstract contract EIP712 {
// EIP-191 Header
string public constant EIP191_HEADER = "\x19\x01";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable is no longer needed after rewriting the getEIP712Hash function

if (!success) {
assembly {
revert(add(result, 32), mload(result))
if (amount > 0) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using nested if statements is more gas-efficient.

assertFalse(SignatureValidator.validateSignature(vm.addr(walletAdminPrivateKey), otherDigest, signature));
}

/// forge-config: default.allow_internal_expect_revert = true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this config setting to resolve the following error:
[FAIL: call didn't revert at a lower depth than cheatcode call depth]

see this: https://book.getfoundry.sh/announcements#-notes

optimizer = true # enable or disable the solc optimizer
optimizer_runs = 1000 # the number of optimizer runs
optimizer_runs = 65536 # the number of optimizer runs
via_ir = true # enable or disable the compilation pipeline for the new IR optimizer
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enabling via-IR: true improves Solidity compilation by introducing an Intermediate Representation (IR) pipeline. This enhances bytecode efficiency, reduces gas costs, and optimizes contract size. It also makes compilation more predictable and future-proof, aligning with Solidity's long-term roadmap

bytes memory opsData = abi.encode(operations);

vm.startPrank(genericSwap);
vm.deal(address(smartOrderStrategy), rfqOffer.takerTokenAmount);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deal cheatcode attempts to mock GenericSwap transferring takerToken to the SmartOrderStrategy contract, but since takerToken is ETH, which is sent during executeStrategy, deal should not be used here.

assertFalse(SignatureValidator.validateSignature(vm.addr(walletAdminPrivateKey), otherDigest, signature));
}

/// forge-config: default.allow_internal_expect_revert = true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reason as mentioned above.

Add this config setting to resolve the following error:
[FAIL: call didn't revert at a lower depth than cheatcode call depth]

see this: https://book.getfoundry.sh/announcements#-notes

{
bytes memory makerSpecificData = abi.encode(defaultAMMPath);
bytes memory strategyData = abi.encode(UNISWAP_SWAP_ROUTER_02_ADDRESS, makerSpecificData);
bytes memory strategyData = abi.encode(UNISWAP_SWAP_ROUTER_02_ADDRESS, order.makerToken, order.makerTokenAmount - fee, makerSpecificData);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the strategyData here is for the mockLimitOrderTaker, which inherits an interface that defines the executeStrategy function. To minimize breaking changes for the mock contract, we simply encode these two parameters in strategyData.

digest = _getDomainSeparator();

// solhint-disable no-inline-assembly
assembly {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those inline assemblies copied from some standard library?

The assembly code was referenced from soladay's implementaion and 1inch's implementation (line 1204~1209)

How much gas does it save?

Basically, it saves 135 gas when testing the getEIP712Hash function. However, the main value of using assembly here is that it helps avoid quadratic memory expansion costs, as we reuse the same memory slot for hashing.

reference: https://x.com/optimizoor/status/1825913380244435408?mx=2

Is it possible to use OZ's 712 implementation?

Our implementation declares some variables as public, while the OZ's implementation declares them as private. I'm not sure whether this difference might cause issues in the front-end or back-end. I will switch to OZ's (or slady) implementation after verifying that it does not break anything on either end

@alex0207s alex0207s merged commit c3ef880 into master Apr 10, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants