Skip to content

Conversation

@nicholaspai
Copy link
Member

@nicholaspai nicholaspai commented Mar 7, 2022

Makes modest gas cost reductions, I think its easy to argue we don't need these optimizations but curious what you think.

Unit test gas report from running yarn test:gas-analytics

Stress testing results

BEFORE:
  Gas Analytics: HubPool Root Bundle Execution
    Pool Rebalance tree with 10 leaves, each containing refunds for 10 different tokens
proposeRootBundle-gasUsed: 110198
      ✔ Simple proposal
executeRootBundle-gasUsed: 714268
      ✔ Executing 1 leaf (220ms)
(average) executeRootBundle-gasUsed: 686317
      ✔ Executing all leaves (1838ms)
(average) executeRootBundle-gasUsed: 497583
      ✔ Executing all leaves using multicall (1506ms)
executeRootBundle-gasUsed: 10362678
      ✔ Stress Test: 1 leaf contains 100 L1 tokens with netSendAmounts > 0 (28541ms)

  Gas Analytics: SpokePool Deposits
    ERC20 Deposits
deposit-gasUsed: 70672
      ✔ 1 Deposit (39ms)
(average) deposit-gasUsed: 70672
      ✔ 10 deposits (264ms)
(average) deposit-gasUsed: 24380
      ✔ 10 deposits using multicall (117ms)
    WETH Deposits
deposit-gasUsed: 62709
      ✔ 1 ETH Deposit

  Gas Analytics: SpokePool Relays
    ERC20 Relays
fillRelay-gasUsed: 83390
      ✔ 1 Relay
(average) fillRelay-gasUsed: 83400
      ✔ 10 Relays (200ms)
(average) fillRelay-gasUsed: 47303
      ✔ 10 relays using multicall (110ms)
    WETH Relays
fillRelay-gasUsed: 98273
      ✔ 1 Relay

  Gas Analytics: SpokePool Relayer Refund Root Execution
    (ERC20) Tree with 10 leaves, each containing 10 refunds
relayRootBundle-gasUsed: 75995
      ✔ Relay proposal
executeRelayerRefundLeaf-gasUsed: 147265
      ✔ Executing 1 leaf (73ms)
(average) executeRelayerRefundLeaf-gasUsed: 131555
      ✔ Executing all leaves (522ms)
(average) executeRelayerRefundLeaf-gasUsed: 102826
      ✔ Executing all leaves using multicall (423ms)
    (WETH): Relayer Refund tree with 10 leaves, each containing 10 refunds
executeRelayerRefundLeaf-gasUsed: 165885
      ✔ Executing 1 leaf (74ms)

  Gas Analytics: SpokePool Slow Relay Root Execution
    (ERC20) Tree with 10 leaves
relayRootBundle-gasUsed: 75995
      ✔ Relay proposal
executeSlowRelayLeaf-gasUsed: 84868
      ✔ Executing 1 leaf
(average) executeSlowRelayLeaf-gasUsed: 99937
      ✔ Executing all leaves (211ms)
(average) executeSlowRelayLeaf-gasUsed: 75462
      ✔ Executing all leaves using multicall (135ms)
    (WETH) Tree with 10 leaves
executeSlowRelayLeaf-gasUsed: 92760
      ✔ Executing 1 leaf

AFTER
  Gas Analytics: HubPool Root Bundle Execution
    Pool Rebalance tree with 10 leaves, each containing refunds for 10 different tokens
proposeRootBundle-gasUsed: 106555
      ✓ Simple proposal
executeRootBundle-gasUsed: 712012
      ✓ Executing 1 leaf
(average) executeRootBundle-gasUsed: 684081
      ✓ Executing all leaves
(average) executeRootBundle-gasUsed: 495349
      ✓ Executing all leaves using multicall
executeRootBundle-gasUsed: 10348429
      ✓ Stress Test: 1 leaf contains 100 L1 tokens with netSendAmounts > 0

  Gas Analytics: SpokePool Deposits
    ERC20 Deposits
deposit-gasUsed: 70581
      ✓ 1 Deposit
(average) deposit-gasUsed: 70581
      ✓ 10 deposits
(average) deposit-gasUsed: 24289
      ✓ 10 deposits using multicall
    WETH Deposits
deposit-gasUsed: 62618
      ✓ 1 ETH Deposit

  Gas Analytics: SpokePool Relays
    ERC20 Relays
fillRelay-gasUsed: 83390
      ✓ 1 Relay
(average) fillRelay-gasUsed: 83400
      ✓ 10 Relays
(average) fillRelay-gasUsed: 47303
      ✓ 10 relays using multicall
    WETH Relays
fillRelay-gasUsed: 98273
      ✓ 1 Relay

  Gas Analytics: SpokePool Relayer Refund Leaf Execution
    (ERC20) Tree with 10 leaves, each containing 10 refunds
relayRootBundle-gasUsed: 75992
      ✓ Relay proposal
executeRelayerRefundLeaf-gasUsed: 146346
      ✓ Executing 1 leaf
(average) executeRelayerRefundLeaf-gasUsed: 130636
      ✓ Executing all leaves
(average) executeRelayerRefundLeaf-gasUsed: 101907
      ✓ Executing all leaves using multicall
    (WETH): Relayer Refund tree with 10 leaves, each containing 10 refunds
executeRelayerRefundLeaf-gasUsed: 164966
      ✓ Executing 1 leaf

  Gas Analytics: SpokePool Slow Relay Root Execution
    (ERC20) Tree with 10 leaves
relayRootBundle-gasUsed: 75992
      ✓ Relay proposal
executeSlowRelayLeaf-gasUsed: 84868
      ✓ Executing 1 leaf
(average) executeSlowRelayLeaf-gasUsed: 99937
      ✓ Executing all leaves
(average) executeSlowRelayLeaf-gasUsed: 75462
      ✓ Executing all leaves using multicall
    (WETH) Tree with 10 leaves
executeSlowRelayLeaf-gasUsed: 92760
      ✓ Executing 1 leaf

Changes

  • In for loops:
    • Pre-increment saves a Store operation
    • Cache length before loop
    • Use unchecked to increment counter
  • Use calldata instead of memory for variables we don't change
  • Pack state variables smarter
  • Don't read from state when emitting variables

Copy link
Member

@chrismaree chrismaree left a comment

Choose a reason for hiding this comment

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

🏌️

@nicholaspai nicholaspai marked this pull request as ready for review March 7, 2022 14:25
l1Token, // l1Token.
l2Token, // l2Token.
uint256(netSendAmounts[i]), // amount.
crossChainContracts[chainId].spokePool // to. This should be the spokePool.
Copy link
Member Author

Choose a reason for hiding this comment

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

Loading this data in each loop adds gas, we should pre-load it instead

Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

As discussed IRL, I think we might want to hold off on merging this until we decide whether we think there's enough time for OZ to review this along with other fixes.

// The bond should be the passed in bondAmount + the final fee.
bondToken = newBondToken;
bondAmount = newBondAmount + _getBondTokenFinalFee();
emit BondSet(address(newBondToken), bondAmount);
Copy link
Member Author

Choose a reason for hiding this comment

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

Don't SLOAD unnecessarily


// L1 tokens length won't be > types(uint32).length
unchecked {
++i;
Copy link
Member Author

Choose a reason for hiding this comment

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

pre-increment saves a STORE vs post-increment

function setFxChild(address newFxChild) public onlyAdmin {
fxChild = newFxChild;
emit SetFxChild(fxChild);
emit SetFxChild(newFxChild);
Copy link
Member Author

Choose a reason for hiding this comment

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

Don't SLOAD unnecessarily

// can be submitted.
RootBundle public rootBundleProposal;

// Whether the bundle proposal process is paused.
Copy link
Member Author

Choose a reason for hiding this comment

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

Any changes in the order of state variables is done to take advantage of variable packing


// WETH contract for Ethereum.
WETH9 public weth;
WETH9 public immutable weth;
Copy link
Member Author

Choose a reason for hiding this comment

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

use immutable where possible

uint256[] memory bundleLpFees,
int256[] memory netSendAmounts,
int256[] memory runningBalances,
uint256[] calldata bundleLpFees,
Copy link
Member Author

Choose a reason for hiding this comment

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

Use calldata for variables that will just be loaded into an in memory PoolRebalance struct


// Before interacting with a particular chain's adapter, ensure that the adapter is set.
require(address(crossChainContracts[chainId].adapter) != address(0), "No adapter for chain");
CrossChainContract memory _crossChainContracts = crossChainContracts[chainId];
Copy link
Member Author

Choose a reason for hiding this comment

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

Reduce one SLOAD

AdapterInterface adapter = crossChainContracts[chainId].adapter;

for (uint32 i = 0; i < l1Tokens.length; i++) {
uint32 length = uint32(l1Tokens.length);
Copy link
Member Author

Choose a reason for hiding this comment

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

cache loop length

@nicholaspai nicholaspai changed the title improve(gas-golf): Use pre-increment and unchecked in loops improve(gas-golf): Various gas golfing Mar 15, 2022
nicholaspai and others added 4 commits March 18, 2022 16:48
Signed-off-by: Matt Rice <matthewcrice32@gmail.com>
@mrice32 mrice32 changed the title improve(gas-golf): Various gas golfing improve(gas-golf): Minor gas tweaks Mar 21, 2022
Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

LGTM!

Signed-off-by: Matt Rice <matthewcrice32@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants