@@ -199,37 +199,66 @@ def test_sstore_erc20_approve(
199
199
# Calculate gas costs
200
200
intrinsic_gas = fork .transaction_intrinsic_cost_calculator ()(calldata = b"" )
201
201
202
- # Cost per iteration (attack contract overhead + approve call)
203
- cost_per_iteration = (
204
- # Attack contract loop overhead
205
- gas_costs .G_VERY_LOW * 2 # MLOAD counter (3*2)
206
- + gas_costs .G_VERY_LOW * 2 # MSTORE selector (3*2)
207
- + gas_costs .G_VERY_LOW * 4 # MLOAD + MSTORE spender + amount (3*4)
208
- + gas_costs .G_BASE # POP (2)
209
- + gas_costs .G_BASE * 3 # SUB + MLOAD + MSTORE for counter decrement (2*3)
210
- + gas_costs .G_BASE * 2 # ISZERO * 2 for loop condition (2*2)
202
+ num_contracts = len (address_stubs .root )
203
+
204
+ # Per-contract fixed overhead (setup + teardown)
205
+ memory_expansion_cost = 15 # Memory expansion to 160 bytes (5 words)
206
+ overhead_per_contract = (
207
+ gas_costs .G_VERY_LOW # MSTORE to initialize counter (3)
208
+ + memory_expansion_cost # Memory expansion (15)
209
+ + gas_costs .G_JUMPDEST # JUMPDEST at loop start (1)
210
+ + gas_costs .G_LOW # MLOAD for While condition check (5)
211
+ + gas_costs .G_BASE # ISZERO (2)
212
+ + gas_costs .G_BASE # ISZERO (2)
211
213
+ gas_costs .G_MID # JUMPI (8)
214
+ + gas_costs .G_BASE # POP to clean up counter at end (2)
215
+ ) # = 38
216
+
217
+ # Cost per iteration (attack contract operations + ERC20 execution)
218
+ cost_per_iteration = (
219
+ # Attack contract loop body operations
220
+ gas_costs .G_VERY_LOW # MSTORE selector at memory[32] (3)
221
+ + gas_costs .G_LOW # MLOAD counter (5)
222
+ + gas_costs .G_VERY_LOW # MSTORE spender at memory[64] (3)
223
+ + gas_costs .G_LOW # MLOAD counter (5)
224
+ + gas_costs .G_VERY_LOW # MSTORE amount at memory[96] (3)
212
225
# CALL to ERC20 contract
213
- + gas_costs .G_WARM_ACCOUNT_ACCESS # Warm CALL to same contract (100)
214
- # Inside ERC20 approve
226
+ + gas_costs .G_WARM_ACCOUNT_ACCESS # Warm CALL base cost (100)
227
+ + gas_costs .G_BASE # POP call result (2)
228
+ # Counter decrement: MSTORE(0, SUB(MLOAD(0), 1))
229
+ + gas_costs .G_LOW # MLOAD counter (5)
230
+ + gas_costs .G_VERY_LOW # PUSH1 1 (3)
231
+ + gas_costs .G_VERY_LOW # SUB (3)
232
+ + gas_costs .G_VERY_LOW # MSTORE counter back (3)
233
+ # While loop condition check
234
+ + gas_costs .G_LOW # MLOAD counter (5)
235
+ + gas_costs .G_BASE # ISZERO (2)
236
+ + gas_costs .G_BASE # ISZERO (2)
237
+ + gas_costs .G_MID # JUMPI back to loop start (8)
238
+ # Inside ERC20 approve function
215
239
+ gas_costs .G_VERY_LOW # PUSH4 selector (3)
216
240
+ gas_costs .G_BASE # EQ selector match (2)
217
241
+ gas_costs .G_MID # JUMPI to function (8)
218
242
+ gas_costs .G_JUMPDEST # JUMPDEST at function start (1)
219
- + gas_costs .G_VERY_LOW * 3 # CALLDATALOAD args (3*3)
243
+ + gas_costs .G_VERY_LOW # CALLDATALOAD spender (3)
244
+ + gas_costs .G_VERY_LOW # CALLDATALOAD amount (3)
220
245
+ gas_costs .G_KECCAK_256 # keccak256 static (30)
221
- + gas_costs .G_KECCAK_256_WORD * 2 # keccak256 dynamic for 64 bytes (2*6 )
246
+ + gas_costs .G_KECCAK_256_WORD * 2 # keccak256 dynamic for 64 bytes (12 )
222
247
+ gas_costs .G_STORAGE_SET # SSTORE to zero slot (20000)
223
- + gas_costs .G_VERY_LOW * 3 # MSTORE result + RETURN setup (3*3)
248
+ + gas_costs .G_VERY_LOW # PUSH1 1 for return value (3)
249
+ + gas_costs .G_VERY_LOW # MSTORE return value (3)
250
+ + gas_costs .G_VERY_LOW # PUSH1 32 for return size (3)
251
+ + gas_costs .G_VERY_LOW # PUSH1 0 for return offset (3)
224
252
# RETURN costs 0 gas
225
- )
253
+ ) # = 20,226
226
254
227
- num_contracts = len (address_stubs .root )
255
+ # Calculate total gas needed
256
+ total_overhead = intrinsic_gas + (overhead_per_contract * num_contracts )
257
+ available_gas_for_iterations = gas_benchmark_value - total_overhead
228
258
229
- # Calculate gas budget per contract and calls per contract
230
- available_gas = gas_benchmark_value - intrinsic_gas
231
- gas_per_contract = available_gas // num_contracts
232
- calls_per_contract = int (gas_per_contract // cost_per_iteration )
259
+ # Calculate calls per contract
260
+ total_iterations_possible = available_gas_for_iterations // cost_per_iteration
261
+ calls_per_contract = total_iterations_possible // num_contracts
233
262
234
263
# Deploy all discovered ERC20 contracts using stubs
235
264
erc20_addresses = []
@@ -243,8 +272,9 @@ def test_sstore_erc20_approve(
243
272
# Log test requirements
244
273
print (
245
274
f"Total gas budget: { gas_benchmark_value / 1_000_000 :.1f} M gas. "
246
- f"~{ gas_per_contract / 1_000_000 :.1f} M gas per contract, "
247
- f"{ calls_per_contract } approve calls per contract."
275
+ f"Intrinsic: { intrinsic_gas } , Overhead per contract: { overhead_per_contract } , "
276
+ f"Cost per iteration: { cost_per_iteration } . "
277
+ f"{ calls_per_contract } approve calls per contract ({ num_contracts } contracts)."
248
278
)
249
279
250
280
# Build attack code that loops through each contract
0 commit comments