Skip to content

Commit d5960ac

Browse files
committed
refactor(tests): Refactor bloatnet SSTORE tests
1 parent 350b900 commit d5960ac

File tree

1 file changed

+208
-52
lines changed

1 file changed

+208
-52
lines changed

tests/benchmark/test_bloatnet.py

Lines changed: 208 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,68 +18,224 @@
1818
)
1919
from ethereum_test_tools.vm.opcode import Opcodes as Op
2020

21-
REFERENCE_SPEC_GIT_PATH = "TODO"
22-
REFERENCE_SPEC_VERSION = "TODO"
2321

24-
25-
@pytest.mark.valid_from("Prague")
26-
@pytest.mark.parametrize("final_storage_value", [0x02 << 248, 0x02])
27-
def test_bloatnet(
28-
blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, final_storage_value: int
22+
@pytest.mark.valid_from("Osaka")
23+
def test_bloatnet_sstore_cold(
24+
blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, gas_benchmark_value: int
2925
):
3026
"""
31-
A test that calls a contract with many SSTOREs.
27+
Benchmark test that maximizes cold SSTORE operations (0 -> 1) by filling
28+
a block with multiple transactions, with each one containing a contract
29+
that performs a set of cold SSTOREs.
3230
33-
The first block will have many SSTORES that go from 0 -> 1
34-
and the 2nd block will have many SSTORES that go from 1 -> 2
31+
The test iteratively creates new transactions until the cumulative gas used
32+
reaches the block's gas benchmark value. Each transaction deploys a contract
33+
that performs as many SSTOREs as possible within the transaction's gas limit.
3534
"""
36-
# Get gas costs for the current fork
3735
gas_costs = fork.gas_costs()
38-
39-
# this is only used for computing the intinsic gas
40-
data = final_storage_value.to_bytes(32, "big").rstrip(b"\x00")
41-
42-
storage = Storage()
43-
44-
# Initial gas for PUSH0 + CALLDATALOAD + POP (at the end)
45-
totalgas = gas_costs.G_BASE * 2 + gas_costs.G_VERY_LOW
46-
totalgas = totalgas + fork.transaction_intrinsic_cost_calculator()(calldata=data)
47-
gas_increment = gas_costs.G_VERY_LOW * 2 + gas_costs.G_STORAGE_SET + gas_costs.G_COLD_SLOAD
48-
sstore_code = Op.PUSH0 + Op.CALLDATALOAD
49-
storage_slot: int = 0
50-
while totalgas + gas_increment < Environment().gas_limit:
51-
totalgas += gas_increment
52-
sstore_code = sstore_code + Op.SSTORE(storage_slot, Op.DUP1)
53-
storage[storage_slot] = final_storage_value
54-
storage_slot += 1
55-
56-
sstore_code = sstore_code + Op.POP # Drop last value on the stack
57-
58-
sender = pre.fund_eoa()
59-
print(sender)
60-
contract_address = pre.deploy_contract(
61-
code=sstore_code,
62-
storage=Storage(),
36+
intrinsic_gas_calc = fork.transaction_intrinsic_cost_calculator()
37+
38+
# TODO: We should maybe not use `None` for tx limit cap as this leads to typing
39+
# issues. Maybe some really high value or the block gas limit?
40+
tx_gas_cap = fork.transaction_gas_limit_cap() or gas_benchmark_value
41+
42+
storage_value = 1
43+
calldata = storage_value.to_bytes(32, "big")
44+
45+
total_sstores = 0
46+
total_block_gas_used = 0
47+
all_txs = []
48+
49+
expected_storage_state = {}
50+
51+
while total_block_gas_used < gas_benchmark_value:
52+
remaining_block_gas = gas_benchmark_value - total_block_gas_used
53+
tx_gas_limit = min(remaining_block_gas, tx_gas_cap)
54+
55+
intrinsic_gas = intrinsic_gas_calc(calldata=calldata)
56+
if tx_gas_limit <= intrinsic_gas:
57+
break
58+
59+
opcode_gas_budget = tx_gas_limit - intrinsic_gas
60+
61+
tx_contract_code = Op.PUSH0 + Op.CALLDATALOAD
62+
tx_opcode_gas = (
63+
gas_costs.G_BASE # PUSH0
64+
+ gas_costs.G_VERY_LOW # CALLDATALOAD
65+
)
66+
tx_sstores_count = 0
67+
68+
current_slot = total_sstores
69+
70+
pop_gas = gas_costs.G_BASE
71+
72+
while True:
73+
sstore_per_op_cost = (
74+
gas_costs.G_VERY_LOW * 2 # PUSH + DUP1
75+
+ gas_costs.G_COLD_SLOAD
76+
+ gas_costs.G_STORAGE_SET # SSTORE
77+
)
78+
79+
if tx_opcode_gas + sstore_per_op_cost + pop_gas > opcode_gas_budget:
80+
break
81+
82+
tx_opcode_gas += sstore_per_op_cost
83+
tx_contract_code += Op.SSTORE(current_slot, Op.DUP1)
84+
tx_sstores_count += 1
85+
current_slot += 1
86+
87+
# If no SSTOREs could be added, we've filled the block
88+
if tx_sstores_count == 0:
89+
break
90+
91+
# Add a POP to clean up the stack at the end
92+
tx_contract_code += Op.POP
93+
tx_opcode_gas += pop_gas
94+
95+
contract_address = pre.deploy_contract(code=tx_contract_code)
96+
tx = Transaction(
97+
to=contract_address,
98+
gas_limit=tx_gas_limit,
99+
data=calldata,
100+
sender=pre.fund_eoa(),
101+
)
102+
all_txs.append(tx)
103+
104+
actual_intrinsic_consumed = intrinsic_gas_calc(
105+
calldata=calldata,
106+
# The actual gas consumed uses the standard intrinsic cost
107+
# (prior execution), not the floor cost used for validation
108+
return_cost_deducted_prior_execution=True,
109+
)
110+
111+
tx_gas_used = actual_intrinsic_consumed + tx_opcode_gas
112+
total_block_gas_used += tx_gas_used
113+
114+
total_sstores += tx_sstores_count
115+
116+
# update expected storage state for each contract
117+
expected_storage_state[contract_address] = Account(
118+
storage=Storage(
119+
{
120+
HashInt(slot): HashInt(storage_value)
121+
for slot in range(current_slot - tx_sstores_count, current_slot)
122+
}
123+
)
124+
)
125+
126+
blockchain_test(
127+
pre=pre,
128+
blocks=[Block(txs=all_txs)],
129+
post=expected_storage_state,
130+
expected_benchmark_gas_used=total_block_gas_used,
63131
)
64132

65-
tx_0_1 = Transaction(
66-
to=contract_address,
67-
gas_limit=Environment().gas_limit,
68-
data=(final_storage_value // 2).to_bytes(32, "big").rstrip(b"\x00"),
69-
value=0,
70-
sender=sender,
71-
)
72-
tx_1_2 = Transaction(
73-
to=contract_address,
74-
gas_limit=Environment().gas_limit,
75-
data=final_storage_value.to_bytes(32, "big").rstrip(b"\x00"),
76-
value=0,
77-
sender=sender,
78-
)
79133

80-
post = {contract_address: Account(storage=storage)}
134+
@pytest.mark.valid_from("Osaka")
135+
def test_bloatnet_sstore_warm(
136+
blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, gas_benchmark_value: int
137+
):
138+
"""
139+
Benchmark test that maximizes warm SSTORE operations (1 -> 2).
81140
82-
blockchain_test(pre=pre, blocks=[Block(txs=[tx_0_1, tx_1_2])], post=post)
141+
This test pre-fills storage slots with value=1, then overwrites them with value=2.
142+
This represents the case of changing a non-zero value to a different non-zero value,
143+
which is cheaper than cold SSTORE but still significant.
144+
"""
145+
gas_costs = fork.gas_costs()
146+
intrinsic_gas_calc = fork.transaction_intrinsic_cost_calculator()
147+
tx_gas_cap = fork.transaction_gas_limit_cap() or gas_benchmark_value
148+
149+
initial_value = 1
150+
new_value = 2
151+
calldata = new_value.to_bytes(32, "big")
152+
153+
total_sstores = 0
154+
total_block_gas_used = 0
155+
all_txs = []
156+
expected_storage_state = {}
157+
158+
while total_block_gas_used < gas_benchmark_value:
159+
remaining_block_gas = gas_benchmark_value - total_block_gas_used
160+
tx_gas_limit = min(remaining_block_gas, tx_gas_cap)
161+
162+
intrinsic_gas = intrinsic_gas_calc(calldata=calldata)
163+
if tx_gas_limit <= intrinsic_gas:
164+
break
165+
166+
opcode_gas_budget = tx_gas_limit - intrinsic_gas
167+
168+
tx_contract_code = Op.PUSH0 + Op.CALLDATALOAD
169+
tx_opcode_gas = (
170+
gas_costs.G_BASE # PUSH0
171+
+ gas_costs.G_VERY_LOW # CALLDATALOAD
172+
)
173+
tx_sstores_count = 0
174+
175+
current_slot = total_sstores
176+
pop_gas = gas_costs.G_BASE
177+
178+
warm_sstore_cost = gas_costs.G_COLD_SLOAD + gas_costs.G_STORAGE_RESET
179+
while True:
180+
sstore_per_op_cost = (
181+
gas_costs.G_VERY_LOW * 2 # PUSH + DUP1
182+
+ warm_sstore_cost # SSTORE
183+
)
184+
185+
if tx_opcode_gas + sstore_per_op_cost + pop_gas > opcode_gas_budget:
186+
break
187+
188+
tx_opcode_gas += sstore_per_op_cost
189+
tx_contract_code += Op.SSTORE(current_slot, Op.DUP1)
190+
tx_sstores_count += 1
191+
current_slot += 1
192+
193+
if tx_sstores_count == 0:
194+
break
195+
196+
tx_contract_code += Op.POP
197+
tx_opcode_gas += pop_gas
198+
199+
# Pre-fill storage with initial values
200+
initial_storage = {
201+
slot: initial_value for slot in range(total_sstores, total_sstores + tx_sstores_count)
202+
}
203+
204+
contract_address = pre.deploy_contract(
205+
code=tx_contract_code,
206+
storage=initial_storage, # type: ignore
207+
)
208+
tx = Transaction(
209+
to=contract_address,
210+
gas_limit=tx_gas_limit,
211+
data=calldata,
212+
sender=pre.fund_eoa(),
213+
)
214+
all_txs.append(tx)
215+
216+
actual_intrinsic_consumed = intrinsic_gas_calc(
217+
calldata=calldata, return_cost_deducted_prior_execution=True
218+
)
219+
220+
tx_gas_used = actual_intrinsic_consumed + tx_opcode_gas
221+
total_block_gas_used += tx_gas_used
222+
total_sstores += tx_sstores_count
223+
224+
expected_storage_state[contract_address] = Account(
225+
storage=Storage(
226+
{
227+
HashInt(slot): HashInt(new_value)
228+
for slot in range(current_slot - tx_sstores_count, current_slot)
229+
}
230+
)
231+
)
232+
233+
blockchain_test(
234+
pre=pre,
235+
blocks=[Block(txs=all_txs)],
236+
post=expected_storage_state,
237+
expected_benchmark_gas_used=total_block_gas_used,
238+
)
83239

84240

85241
# Warm reads are very cheap, which means you can really fill a block

0 commit comments

Comments
 (0)