Skip to content

Commit 5834cc6

Browse files
committed
fix(benchmark): correct SSTORE benchmark gas calculation
Fixed gas calculation for test_sstore_erc20_approve to ensure accurate gas usage prediction and prevent transaction reverts: Key fixes: - Added memory expansion cost (15 gas per contract) - Corrected G_LOW gas values in comments (5 gas, not 3) - Separated per-contract overhead from per-iteration costs - Improved cost calculation clarity with detailed opcode breakdown Gas calculation (10M gas, 3 contracts): - Intrinsic: 21,000 - Overhead per contract: 38 - Cost per iteration: 20,226 - Calls per contract: 164 - Expected gas used: 9,972,306 (99.72% utilization)
1 parent 4843bc3 commit 5834cc6

File tree

1 file changed

+52
-22
lines changed

1 file changed

+52
-22
lines changed

tests/benchmark/stateful/bloatnet/test_single_opcode.py

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -199,37 +199,66 @@ def test_sstore_erc20_approve(
199199
# Calculate gas costs
200200
intrinsic_gas = fork.transaction_intrinsic_cost_calculator()(calldata=b"")
201201

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)
211213
+ 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)
212225
# 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
215239
+ gas_costs.G_VERY_LOW # PUSH4 selector (3)
216240
+ gas_costs.G_BASE # EQ selector match (2)
217241
+ gas_costs.G_MID # JUMPI to function (8)
218242
+ 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)
220245
+ 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)
222247
+ 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)
224252
# RETURN costs 0 gas
225-
)
253+
) # = 20,226
226254

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
228258

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
233262

234263
# Deploy all discovered ERC20 contracts using stubs
235264
erc20_addresses = []
@@ -243,8 +272,9 @@ def test_sstore_erc20_approve(
243272
# Log test requirements
244273
print(
245274
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)."
248278
)
249279

250280
# Build attack code that loops through each contract

0 commit comments

Comments
 (0)