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

refactor(contracts/core): improved variable packing to reduce gas costs #2291

Merged
merged 4 commits into from
Oct 28, 2024

Conversation

Zodomo
Copy link
Contributor

@Zodomo Zodomo commented Oct 25, 2024

I noticed that the values used in FeeOracleV2 could be better optimized. To start, this was the original storage layout:

| Name         | Type                                             | Slot | Offset | Bytes | Contract                               |
|--------------|--------------------------------------------------|------|--------|-------|----------------------------------------|
| baseGasLimit | uint64                                           | 0    | 0      | 8     | src/xchain/FeeOracleV2.sol:FeeOracleV2 |
| protocolFee  | uint256                                          | 1    | 0      | 32    | src/xchain/FeeOracleV2.sol:FeeOracleV2 |
| manager      | address                                          | 2    | 0      | 20    | src/xchain/FeeOracleV2.sol:FeeOracleV2 |
| _feeParams   | mapping(uint64 => struct IFeeOracleV2.FeeParams) | 3    | 0      | 32    | src/xchain/FeeOracleV2.sol:FeeOracleV2 |

Four slots is a bit excessive, especially since the values we're working with are quite small. After some thoughtful resizing, this is what I reduced it to:

| Name         | Type                                             | Slot | Offset | Bytes | Contract                               |
|--------------|--------------------------------------------------|------|--------|-------|----------------------------------------|
| protocolFee  | uint64                                           | 0    | 0      | 8     | src/xchain/FeeOracleV2.sol:FeeOracleV2 |
| baseGasLimit | uint32                                           | 0    | 8      | 4     | src/xchain/FeeOracleV2.sol:FeeOracleV2 |
| manager      | address                                          | 0    | 12     | 20    | src/xchain/FeeOracleV2.sol:FeeOracleV2 |
| _feeParams   | mapping(uint64 => struct IFeeOracleV2.FeeParams) | 1    | 0      | 32    | src/xchain/FeeOracleV2.sol:FeeOracleV2 |

By reducing it from 4 slots to 2, much fewer SLOADs and SSTOREs are required for both all reads and writes.

I pushed this optimization even further by restructuring IFeeOracleV2.FeeParams. Here's the original structure, followed by the optimized one:

Original:
struct FeeParams {
    uint64 chainId;
    uint256 execGasPrice;
    uint256 dataGasPrice;
    uint256 toNativeRate;
}

Revised:
struct FeeParams {
    uint64 chainId;
    uint64 execGasPrice;
    uint64 dataGasPrice;
    uint64 toNativeRate;
}

By reducing the sizing of the uint256 variables to uint64, all four variables fit within a single storage slot. This reduces R/W operations to _feeParams substantially from 5 slots to 2. Beforehand, you'd have one R/W to get the location, and then four R/W for each parameter. Because slots are 256 bits in size, the revised version in its entirety only requires one for the location and one for the R/W operation.

To justify these changes, I looked at the numbers and figured out how high they could reasonably go. The baseGasLimit was originally a uint64, however, I do not think ethereum's base gas limit will ever approach 18_446_744_073_709_551_615, we have a long way from ~30-45M before we get there. A uint32 allows for 4_294_967_295, which is already well above existing capacity. With this reduced to 32 bits, and an address taking up 160 bits, we had 64 bits remaining. Conveniently, this is enough for the protocolFee, as I do not expect Omni to ever charge higher than 18.4 ETH for a single transaction.

As for the gas prices themselves, and the native rate, none of these values will ever exceed uint64. If the gas price ever reached 18.4 ETH, we'd have worse problems on our hands to deal with, and I doubt anyone will be paying those gas prices for a crosschain transaction.

The protocolFee and baseGasLimit are the only parameters that really need any consideration. If we do not expect to ever raise this above 50k, we should use a uint16 (65_535) to store it, allowing us to increase the protocolFee to a uint80. The reasoning for this is because it would better support L2's with alternative gas tokens by having a protocol fee ceiling of ~1.2M tokens, as opposed to ~18.4 tokens. For chains that use ETH, the current configuration is fine, but there may come a time where we support L2s that don't.

After all of these changes, I was able to reduce the costs of the following function calls (via test gas metering):

test_feeFor() (gas: -9189 (-8.209%)) 
test_bulkSetFeeParams() (gas: -97549 (-44.746%))

issue: #1951

@Zodomo Zodomo self-assigned this Oct 25, 2024
@Zodomo Zodomo linked an issue Oct 25, 2024 that may be closed by this pull request
Copy link
Contributor

@kevinhalliday kevinhalliday left a comment

Choose a reason for hiding this comment

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

Nice - there's a file contracts/core/proxy.json that I don't think we want but other than that looks good

@Zodomo
Copy link
Contributor Author

Zodomo commented Oct 28, 2024

Removed the unnecessary ABI file. @kevinhalliday what do you think about the final point in the PR? To reiterate:

The protocolFee and baseGasLimit are the only parameters that really need any consideration. If we do not expect to ever raise this above 50k, we should use a uint16 (65_535) to store it, allowing us to increase the protocolFee to a uint80. The reasoning for this is because it would better support L2's with alternative gas tokens by having a protocol fee ceiling of ~1.2M tokens, as opposed to ~18.4 tokens. For chains that use ETH, the current configuration is fine, but there may come a time where we support L2s that don't.

I am personally of the mind that the baseGasLimit shouldn't be over 50k. If we're never going to raise that to something like 100k, bumping protocolFee to uint80 is a good idea.

@Zodomo Zodomo merged commit a80ae44 into main Oct 28, 2024
18 checks passed
@Zodomo Zodomo deleted the zodomo/feeoraclev2 branch October 28, 2024 18:21
@Zodomo Zodomo mentioned this pull request Oct 28, 2024
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.

FeeOracleV2
2 participants