Skip to content

Commit 964e9bb

Browse files
committed
evm: quoter gas optimizations
1 parent 98e4dc1 commit 964e9bb

File tree

3 files changed

+126
-46
lines changed

3 files changed

+126
-46
lines changed

evm/src/ExecutorQuoter.sol

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,19 @@ contract ExecutorQuoter is IExecutorQuoter {
2828
uint64 dstPrice;
2929
}
3030

31-
struct ChainDecimals {
31+
struct ChainInfo {
3232
bool enabled;
3333
uint8 gasPriceDecimals;
3434
uint8 nativeDecimals;
3535
}
3636

37-
struct QuoteUpdate {
37+
struct Update {
3838
uint16 chainId;
39-
OnChainQuoteBody quote;
40-
}
41-
42-
struct DecimalsUpdate {
43-
uint16 chainId;
44-
ChainDecimals decimals;
39+
bytes32 update;
4540
}
4641

4742
mapping(uint16 => OnChainQuoteBody) public quoteByDstChain;
48-
mapping(uint16 => ChainDecimals) public decimalsByDstChain;
43+
mapping(uint16 => ChainInfo) public chainInfos;
4944

5045
/// @dev Selector 0x40788bb5.
5146
error InvalidUpdater(address sender, address expected);
@@ -56,43 +51,57 @@ contract ExecutorQuoter is IExecutorQuoter {
5651
/// @dev Selector 0x3a5a1720.
5752
error MoreThanOneDropOff();
5853

54+
modifier onlyUpdater() {
55+
if (msg.sender != updaterAddress) {
56+
revert InvalidUpdater(msg.sender, updaterAddress);
57+
}
58+
_;
59+
}
60+
5961
constructor(address _quoterAddress, address _updaterAddress, uint8 _srcTokenDecimals, bytes32 _payeeAddress) {
6062
quoterAddress = _quoterAddress;
6163
updaterAddress = _updaterAddress;
6264
srcTokenDecimals = _srcTokenDecimals;
6365
payeeAddress = _payeeAddress;
6466
}
6567

66-
function decimalsUpdate(DecimalsUpdate[] calldata updates) public {
67-
if (msg.sender != updaterAddress) {
68-
revert InvalidUpdater(msg.sender, updaterAddress);
69-
}
70-
uint256 updatesLength = updates.length;
71-
for (uint256 i = 0; i < updatesLength;) {
72-
DecimalsUpdate memory update = updates[i];
73-
decimalsByDstChain[update.chainId] = update.decimals;
74-
unchecked {
75-
i += 1;
68+
function _batchUpdate(Update[] calldata updates, uint256 mappingSlot) private {
69+
assembly {
70+
let len := updates.length
71+
let baseOffset := updates.offset
72+
73+
for { let i := 0 } lt(i, len) { i := add(i, 1) } {
74+
// Load update directly from calldata
75+
let updatePtr := add(baseOffset, mul(i, 0x40))
76+
let chainId := calldataload(updatePtr)
77+
let newValue := calldataload(add(updatePtr, 0x20))
78+
79+
// Calculate storage slot for mapping[chainId]
80+
mstore(0x00, chainId)
81+
mstore(0x20, mappingSlot)
82+
let slot := keccak256(0x00, 0x40)
83+
sstore(slot, newValue)
7684
}
7785
}
7886
}
7987

80-
// TODO: pack these updates instead to save l2 cost
81-
function quoteUpdate(QuoteUpdate[] calldata updates) public {
82-
if (msg.sender != updaterAddress) {
83-
revert InvalidUpdater(msg.sender, updaterAddress);
88+
function chainInfoUpdate(Update[] calldata updates) external onlyUpdater {
89+
uint256 slot;
90+
assembly {
91+
slot := chainInfos.slot
8492
}
85-
uint256 updatesLength = updates.length;
86-
for (uint256 i = 0; i < updatesLength;) {
87-
QuoteUpdate memory update = updates[i];
88-
quoteByDstChain[update.chainId] = update.quote;
89-
unchecked {
90-
i += 1;
91-
}
93+
_batchUpdate(updates, slot);
94+
}
95+
96+
function quoteUpdate(Update[] calldata updates) external onlyUpdater {
97+
uint256 slot;
98+
assembly {
99+
slot := quoteByDstChain.slot
92100
}
101+
_batchUpdate(updates, slot);
93102
}
94103

95-
function normalize(uint256 amount, uint8 from, uint8 to) internal pure returns (uint256) {
104+
function normalize(uint256 amount, uint8 from, uint8 to) private pure returns (uint256) {
96105
if (from > to) {
97106
return amount / 10 ** uint256(from - to);
98107
} else if (from < to) {
@@ -101,11 +110,11 @@ contract ExecutorQuoter is IExecutorQuoter {
101110
return amount;
102111
}
103112

104-
function mul(uint256 a, uint256 b, uint8 decimals) internal pure returns (uint256) {
113+
function mul(uint256 a, uint256 b, uint8 decimals) private pure returns (uint256) {
105114
return (a * b) / 10 ** uint256(decimals);
106115
}
107116

108-
function div(uint256 a, uint256 b, uint8 decimals) internal pure returns (uint256) {
117+
function div(uint256 a, uint256 b, uint8 decimals) private pure returns (uint256) {
109118
return (a * 10 ** uint256(decimals)) / b;
110119
}
111120

@@ -115,7 +124,7 @@ contract ExecutorQuoter is IExecutorQuoter {
115124
/// - `GasDropOffInstruction` contributes only to `msgValue`.
116125
/// Throws If an unsupported instruction type is encountered.
117126
function totalGasLimitAndMsgValue(bytes calldata relayInstructions)
118-
internal
127+
private
119128
pure
120129
returns (uint256 gasLimit, uint256 msgValue)
121130
{
@@ -157,23 +166,22 @@ contract ExecutorQuoter is IExecutorQuoter {
157166

158167
function estimateQuote(
159168
OnChainQuoteBody storage quote,
160-
ChainDecimals storage dstChainDecimals,
169+
ChainInfo storage dstChainInfo,
161170
uint256 gasLimit,
162171
uint256 msgValue
163-
) internal view returns (uint256) {
172+
) private view returns (uint256) {
164173
uint256 srcChainValueForBaseFee = normalize(quote.baseFee, QUOTE_DECIMALS, srcTokenDecimals);
165174

166175
uint256 nSrcPrice = normalize(quote.srcPrice, QUOTE_DECIMALS, DECIMAL_RESOLUTION);
167176
uint256 nDstPrice = normalize(quote.dstPrice, QUOTE_DECIMALS, DECIMAL_RESOLUTION);
168177
uint256 scaledConversion = div(nDstPrice, nSrcPrice, DECIMAL_RESOLUTION);
169178

170179
uint256 nGasLimitCost =
171-
normalize(gasLimit * quote.dstGasPrice, dstChainDecimals.gasPriceDecimals, DECIMAL_RESOLUTION);
172-
180+
normalize(gasLimit * quote.dstGasPrice, dstChainInfo.gasPriceDecimals, DECIMAL_RESOLUTION);
173181
uint256 srcChainValueForGasLimit =
174182
normalize(mul(nGasLimitCost, scaledConversion, DECIMAL_RESOLUTION), DECIMAL_RESOLUTION, srcTokenDecimals);
175183

176-
uint256 nMsgValue = normalize(msgValue, dstChainDecimals.nativeDecimals, DECIMAL_RESOLUTION);
184+
uint256 nMsgValue = normalize(msgValue, dstChainInfo.nativeDecimals, DECIMAL_RESOLUTION);
177185
uint256 srcChainValueForMsgValue =
178186
normalize(mul(nMsgValue, scaledConversion, DECIMAL_RESOLUTION), DECIMAL_RESOLUTION, srcTokenDecimals);
179187
return srcChainValueForBaseFee + srcChainValueForGasLimit + srcChainValueForMsgValue;
@@ -185,15 +193,15 @@ contract ExecutorQuoter is IExecutorQuoter {
185193
address, //refundAddr,
186194
bytes calldata, //requestBytes,
187195
bytes calldata relayInstructions
188-
) public view returns (bytes32, uint256) {
189-
ChainDecimals storage dstChainDecimals = decimalsByDstChain[dstChain];
190-
if (!dstChainDecimals.enabled) {
196+
) external view returns (bytes32, uint256) {
197+
ChainInfo storage dstChainInfo = chainInfos[dstChain];
198+
if (!dstChainInfo.enabled) {
191199
revert ChainDisabled(dstChain);
192200
}
193201
OnChainQuoteBody storage quote = quoteByDstChain[dstChain];
194202
(uint256 gasLimit, uint256 msgValue) = totalGasLimitAndMsgValue(relayInstructions);
195203
// NOTE: this does not include any maxGasLimit or maxMsgValue checks
196-
uint256 requiredPayment = estimateQuote(quote, dstChainDecimals, gasLimit, msgValue);
204+
uint256 requiredPayment = estimateQuote(quote, dstChainInfo, gasLimit, msgValue);
197205

198206
return (payeeAddress, requiredPayment);
199207
}

evm/src/ExecutorQuoterRouter.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ contract ExecutorQuoterRouter is IExecutorQuoterRouter {
3737
ourChain = executor.ourChain();
3838
}
3939

40-
function updateQuoterContract(bytes calldata gov) public {
40+
function updateQuoterContract(bytes calldata gov) external {
4141
bytes4 prefix;
4242
uint16 chainId;
4343
uint160 quoter;
@@ -88,7 +88,7 @@ contract ExecutorQuoterRouter is IExecutorQuoterRouter {
8888
address quoterAddr,
8989
bytes calldata requestBytes,
9090
bytes calldata relayInstructions
91-
) public view returns (uint256 requiredPayment) {
91+
) external view returns (uint256 requiredPayment) {
9292
(, requiredPayment) =
9393
quoterContract[quoterAddr].requestQuote(dstChain, dstAddr, refundAddr, requestBytes, relayInstructions);
9494
}
@@ -100,7 +100,7 @@ contract ExecutorQuoterRouter is IExecutorQuoterRouter {
100100
address quoterAddr,
101101
bytes calldata requestBytes,
102102
bytes calldata relayInstructions
103-
) public payable {
103+
) external payable {
104104
IExecutorQuoter implementation = quoterContract[quoterAddr];
105105
(bytes32 payeeAddress, uint256 requiredPayment) =
106106
implementation.requestQuote(dstChain, dstAddr, refundAddr, requestBytes, relayInstructions);

evm/test/ExecutorQuoter.t.sol

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.13;
3+
4+
import {Test} from "forge-std/Test.sol";
5+
import {ExecutorQuoter} from "../src/ExecutorQuoter.sol";
6+
7+
contract ExecutorQuoterTest is Test {
8+
ExecutorQuoter public executorQuoter;
9+
ExecutorQuoter.Update[] public updates;
10+
ExecutorQuoter.Update[] public chainInfoUpdates;
11+
12+
address constant updater = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496;
13+
bytes32 constant chainInfoUpdatePacked = 0x0000000000000000000000000000000000000000000000000000000000121201;
14+
uint16 constant dstChain = 10003;
15+
bytes32 constant dstAddr = bytes32(0);
16+
17+
function packUint64(uint64 a, uint64 b, uint64 c, uint64 d) public pure returns (bytes32) {
18+
return bytes32((uint256(a) << 192) | (uint256(b) << 128) | (uint256(c) << 64) | uint256(d));
19+
}
20+
21+
function setUp() public {
22+
executorQuoter = new ExecutorQuoter(updater, updater, 18, bytes32(uint256(uint160(updater))));
23+
ExecutorQuoter.Update memory chainInfoUpdate;
24+
chainInfoUpdate.chainId = 10003;
25+
chainInfoUpdate.update = chainInfoUpdatePacked;
26+
chainInfoUpdates.push(chainInfoUpdate);
27+
executorQuoter.chainInfoUpdate(chainInfoUpdates);
28+
ExecutorQuoter.OnChainQuoteBody memory quote1;
29+
quote1.baseFee = 27971;
30+
quote1.dstGasPrice = 100000000;
31+
quote1.srcPrice = 35751300000000;
32+
quote1.dstPrice = 35751300000000;
33+
ExecutorQuoter.Update memory update1;
34+
update1.chainId = 10003;
35+
update1.update = packUint64(quote1.baseFee, quote1.dstGasPrice, quote1.srcPrice, quote1.dstPrice);
36+
ExecutorQuoter.OnChainQuoteBody memory quote2;
37+
quote2.baseFee = 27971;
38+
quote2.dstGasPrice = 1000250;
39+
quote2.srcPrice = 35751300000000;
40+
quote2.dstPrice = 35751300000000;
41+
ExecutorQuoter.Update memory update2;
42+
update2.chainId = 10005;
43+
update2.update = packUint64(quote2.baseFee, quote2.dstGasPrice, quote2.srcPrice, quote2.dstPrice);
44+
ExecutorQuoter.OnChainQuoteBody memory quote3;
45+
quote3.baseFee = 27971;
46+
quote3.dstGasPrice = 1000078;
47+
quote3.srcPrice = 35751300000000;
48+
quote3.dstPrice = 35751300000000;
49+
ExecutorQuoter.Update memory update3;
50+
update3.chainId = 10003;
51+
update3.update = packUint64(quote3.baseFee, quote3.dstGasPrice, quote3.srcPrice, quote3.dstPrice);
52+
updates.push(update1);
53+
updates.push(update2);
54+
updates.push(update3);
55+
// store first so the gas metric is on a non-zero slot
56+
executorQuoter.quoteUpdate(updates);
57+
}
58+
59+
function test_quoteUpdate() public {
60+
executorQuoter.quoteUpdate(updates);
61+
}
62+
63+
function test_fuzz_quoteUpdate(ExecutorQuoter.Update[] calldata _updates) public {
64+
executorQuoter.quoteUpdate(_updates);
65+
}
66+
67+
function test_requestQuote() public view {
68+
executorQuoter.requestQuote(
69+
dstChain, dstAddr, updater, abi.encodePacked(""), abi.encodePacked(uint8(1), uint128(250000))
70+
);
71+
}
72+
}

0 commit comments

Comments
 (0)