@@ -11,249 +11,48 @@ import {BranchlessMath} from "./utils/BranchlessMath.sol";
1111library GasUtils {
1212 using BranchlessMath for uint256 ;
1313
14- /**
15- * @dev How much gas is used until the first `gasleft()` instruction is executed in the `Gateway.batchExecute` method.
16- *
17- * HOW TO UPDATE THIS VALUE:
18- * 1. Run `forge test --match-test=test_gasMeter --fuzz-runs=1 --debug`
19- * 2. Move the cursor until you enter the `src/Gateway.sol` file.
20- * 3. Execute the opcodes until you reach the first `GAS` opcode.
21- * 4. Execute the GAS opcode then copy the `Gas used in call` value to the constant below.
22- *
23- * Obs: To guarantee the overhead is constant regardless the input size, always use `calldata` instead of `memory`
24- * for external functions.
25- */
26- uint256 internal constant BATCH_SELECTOR_OVERHEAD = 465 ;
27-
28- /**
29- * @dev How much gas is used until the first `gasleft()` instruction is executed.
30- *
31- * HOW TO UPDATE THIS VALUE:
32- * 1. Run `forge test --match-test=test_submitMessageMeter --fuzz-runs=1 --debug`
33- * 2. Move the cursor until you enter the `src/Gateway.sol` file.
34- * 3. Execute the opcodes until you reach the first `GAS` opcode.
35- * 4. Execute the GAS opcode then copy the `Gas used in call` value to the constant below.
36- *
37- * Obs: To guarantee the overhead is constant regardless the input size, always use `calldata` instead of `memory`
38- * for external functions.
39- */
40- uint256 internal constant EXECUTION_SELECTOR_OVERHEAD = 452 ;
41-
42- /**
43- * @dev Base cost of the `IExecutor.execute` method.
44- */
45- uint256 internal constant EXECUTION_BASE_COST = EXECUTION_SELECTOR_OVERHEAD + 46960 + 69 + 2180 ;
46-
47- /**
48- * @dev Solidity's reserved location for the free memory pointer.
49- * Reference: https://docs.soliditylang.org/en/v0.8.28/internals/layout_in_memory.html
50- */
51- uint256 internal constant ALLOCATED_MEMORY = 0x40 ;
52-
53- /**
54- * @dev Read the current allocated size (a.k.a free memory pointer).
55- */
56- function readAllocatedMemory () internal pure returns (uint256 pointer ) {
57- assembly ("memory-safe" ) {
58- pointer := mload (ALLOCATED_MEMORY)
59- }
60- }
61-
62- /**
63- * @dev Replace the current allocated size by the `newPointer`, and returns the old value stored.
64- * CAUTION: Only use this method if you know what you are doing. Make sure you don't overwrite any
65- * memory location that is still in use by the current call context.
66- */
67- function unsafeReplaceAllocatedMemory (uint256 newPointer ) internal pure returns (uint256 oldPointer ) {
68- assembly ("memory-safe" ) {
69- oldPointer := mload (ALLOCATED_MEMORY)
70- mstore (ALLOCATED_MEMORY, newPointer)
71- }
72- }
73-
74- /**
75- * @dev Compute the gas cost of memory expansion.
76- * @param words number of words, where a word is 32 bytes
77- */
78- function memoryExpansionGasCost (uint256 words ) private pure returns (uint256 ) {
79- unchecked {
80- return (words.saturatingMul (words) >> 9 ).saturatingAdd (words.saturatingMul (3 ));
81- }
82- }
83-
8414 /**
8515 * @dev Compute the amount of gas used by the `GatewayProxy`.
8616 * @param calldataLen The length of the calldata in bytes
87- * @param returnLen The length of the return data in bytes
8817 */
89- function proxyOverheadGasCost (uint256 calldataLen , uint256 returnLen ) internal pure returns (uint256 ) {
18+ function proxyOverheadGas (uint256 calldataLen ) internal pure returns (uint256 ) {
9019 unchecked {
91- // Convert the calldata and return data length to words
92- calldataLen = _toWord (calldataLen);
93- returnLen = _toWord (returnLen);
94-
9520 // Base cost: OPCODES + COLD SLOAD + COLD DELEGATECALL + RETURNDATACOPY
96- // uint256 gasCost = 57 + 2100 + 2600;
97- uint256 gasCost = 31 + 2100 + 2600 + 32 + 66 ;
21+ uint256 gas = 31 + 2100 + 2600 + 32 + 66 ;
9822
9923 // CALLDATACOPY
100- gasCost = gasCost. saturatingAdd (calldataLen * 3 ) ;
24+ gas += calldataLen. toWordCount () * 3 ;
10125
10226 // RETURNDATACOPY
103- gasCost = gasCost. saturatingAdd (returnLen * 3 ) ;
27+ // gas += returnLen.toWordCount() * 3;
10428
10529 // MEMORY EXPANSION (minimal 3 due mstore(0x40, 0x80))
106- uint256 words = calldataLen.max (returnLen).max (3 );
107- gasCost = gasCost.saturatingAdd (memoryExpansionGasCost (words));
108- return gasCost;
109- }
110- }
111-
112- /**
113- * @dev Estimate the gas cost of a GMP message.
114- * @param dataNonZeros The number of non-zero bytes in the gmp data.
115- * @param dataZeros The number of zero bytes in the gmp data.
116- * @param gasLimit The message gas limit.
117- */
118- function estimateGas (uint16 dataNonZeros , uint16 dataZeros , uint256 gasLimit ) internal pure returns (uint256 ) {
119- uint256 messageSize = uint256 (dataNonZeros) + uint256 (dataZeros);
120- unchecked {
121- // add execution cost
122- uint256 gasCost = executionGasUsed (uint16 (BranchlessMath.min (messageSize, type (uint16 ).max)), gasLimit);
123- // add base cost
124- gasCost = gasCost.saturatingAdd (21000 );
125-
126- // calldata zero bytes
127- uint256 zeros = 31 + 30 + 12 + 30 + 31 + 30 ;
128- zeros = zeros.saturatingAdd ((messageSize.saturatingAdd (31 ) & 0xffffe0 ) - uint256 (dataZeros));
129- gasCost = gasCost.saturatingAdd (zeros.saturatingMul (4 ));
130-
131- // calldata non-zero bytes
132- uint256 nonZeros = uint256 (dataNonZeros).saturatingAdd (4 + 96 + 1 + 32 + 2 + 20 + 2 + 32 + 32 + 1 + 2 );
133- gasCost = gasCost.saturatingAdd (nonZeros.saturatingMul (16 ));
134-
135- return gasCost;
136- }
137- }
138-
139- /**
140- * @dev Convert byte count to 256bit word count, rounded up.
141- */
142- function _toWord (uint256 byteCount ) private pure returns (uint256 words ) {
143- assembly {
144- words := add (shr (5 , byteCount), gt (and (byteCount, 0x1f ), 0 ))
145- }
146- }
147-
148- function _executionGasCost (uint256 messageSize , uint256 gasUsed ) internal pure returns (uint256 ) {
149- // Safety: The operations below can't overflow because the message size can't be greater than 2**16
150- unchecked {
151- // cost of calldata copy
152- uint256 gas = _toWord (messageSize) * 3 ;
153- // cost of hashing the payload
154- gas = gas.saturatingAdd (_toWord (messageSize) * 6 );
155- gas = gas.saturatingAdd (gasUsed);
156- uint256 memoryExpansion = messageSize.align32 () + 676 ;
157- {
158- // Selector + Signature + GmpMessage
159- uint256 words = messageSize.align32 ().saturatingAdd (388 + 31 ) >> 5 ;
160- words = (words * 106 ) + (((words.saturatingSub (255 ) + 254 ) / 255 ) * 214 );
161- gas = gas.saturatingAdd (words);
162- }
163- gas = gas.saturatingAdd (EXECUTION_BASE_COST);
164- gas = gas.saturatingAdd (memoryExpansionGasCost (_toWord (memoryExpansion)));
30+ gas += memoryExpansionGas (calldataLen.toWordCount ());
16531 return gas;
16632 }
16733 }
16834
16935 /**
170- * @dev Compute the inverse of `N - floor(N / 64)` defined by EIP-150, used to
171- * compute the gas needed for a transaction.
172- */
173- function inverseOfAllButOne64th (uint256 x ) internal pure returns (uint256 inverse ) {
174- unchecked {
175- // inverse = (x * 64) / 63
176- inverse = x.saturatingShl (6 ).saturatingDiv (63 );
177-
178- // Subtract 1 if `inverse` is a multiple of 64 and greater than 0
179- inverse -= BranchlessMath.toUint (inverse > 0 && (inverse % 64 ) == 0 );
180- }
181- }
182-
183- /**
184- * @dev Compute the gas that should be refunded to the executor for the execution.
185- * @param messageSize The size of the message.
186- * @param gasUsed The gas used by the gmp message.
36+ * @dev Compute the gas cost of memory expansion.
37+ * @param words number of words, where a word is 32 bytes
18738 */
188- function executionGasUsed (uint16 messageSize , uint256 gasUsed ) internal pure returns (uint256 executionCost ) {
189- // Add the base `IExecutor.execute` gas cost.
190- executionCost = _executionGasCost (messageSize, gasUsed);
191-
192- // Add `GatewayProxy` gas overhead
39+ function memoryExpansionGas (uint256 words ) internal pure returns (uint256 ) {
19340 unchecked {
194- // Safety: The operations below can't overflow because the message size can't be greater than 2**16
195- uint256 calldataSize = ((uint256 (messageSize) + 31 ) & 0xffffe0 ) + 388 ; // selector + Signature + GmpMessage
196- executionCost = executionCost.saturatingAdd (proxyOverheadGasCost (calldataSize, 64 ));
41+ return ((words * words) >> 9 ) + words * 3 ;
19742 }
19843 }
19944
20045 /**
20146 * @dev Compute the transaction base cost.
20247 */
203- function txBaseCost () internal pure returns (uint256 ) {
48+ function txBaseGas () internal pure returns (uint256 ) {
20449 unchecked {
20550 uint256 nonZeros = countNonZerosCalldata (msg .data );
20651 uint256 zeros = msg .data .length - nonZeros;
20752 return 21000 + (nonZeros * 16 ) + (zeros * 4 );
20853 }
20954 }
21055
211- /**
212- * @dev Count the number of non-zero bytes in a byte sequence from memory.
213- * gas cost = 217 + (words * 112) + ((words - 1) * 193)
214- */
215- function countNonZeros (bytes memory data ) internal pure returns (uint256 nonZeros ) {
216- assembly ("memory-safe" ) {
217- // Efficient algorithm for counting non-zero bytes in parallel
218- let size := mload (data)
219-
220- // Temporary set the length of the data to zero
221- mstore (data, 0 )
222-
223- nonZeros := 0
224- for {
225- // 32 byte aligned pointer, ex: if data.length is 54, then `ptr = data + 32`
226- let ptr := add (data, and (add (size, 31 ), 0xffffffe0 ))
227- let end := xor (data, mul (xor (sub (ptr, 480 ), data), gt (sub (ptr, data), 480 )))
228- } true { end := xor (data, mul (xor (sub (ptr, 480 ), data), gt (sub (ptr, data), 480 ))) } {
229- // Normalize and count non-zero bytes in parallel
230- let v := 0
231- for {} gt (ptr, end) { ptr := sub (ptr, 32 ) } {
232- let r := mload (ptr)
233- r := or (r, shr (4 , r))
234- r := or (r, shr (2 , r))
235- r := or (r, shr (1 , r))
236- r := and (r, 0x0101010101010101010101010101010101010101010101010101010101010101 )
237- v := add (v, r)
238- }
239-
240- // Sum bytes in parallel
241- v := add (v, shr (128 , v))
242- v := add (v, shr (64 , v))
243- v := add (v, shr (32 , v))
244- v := add (v, shr (16 , v))
245- v := and (v, 0xffff )
246- v := add (and (v, 0xff ), shr (8 , v))
247- nonZeros := add (nonZeros, v)
248-
249- if eq (ptr, data) { break }
250- }
251-
252- // Restore the original length of the data
253- mstore (data, size)
254- }
255- }
256-
25756 /**
25857 * @dev Count the number of non-zero bytes from calldata.
25958 * gas cost = 224 + (words * 106) + (((words + 254) / 255) * 214)
0 commit comments