Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.

Commit d9e425d

Browse files
authored
Cleanup gateway (#55)
1 parent b3b8493 commit d9e425d

File tree

12 files changed

+589
-1203
lines changed

12 files changed

+589
-1203
lines changed

src/GasUtils.sol

Lines changed: 10 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -11,249 +11,48 @@ import {BranchlessMath} from "./utils/BranchlessMath.sol";
1111
library 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

Comments
 (0)