Skip to content

Commit b3aa527

Browse files
fselmogballet
andauthored
refactor(tests): Proposed patch for bloatnet SSTORE tests (#1)
* refactor(tests): Proposed patch for bloatnet SSTORE tests * refactor(tests): Update tests from comments on PR PR: #1 Signed-off-by: fselmo <[email protected]> * Use parametrization of the value that is written to --------- Signed-off-by: fselmo <[email protected]> Co-authored-by: Guillaume Ballet <[email protected]>
1 parent 6d12da6 commit b3aa527

File tree

1 file changed

+179
-48
lines changed

1 file changed

+179
-48
lines changed

tests/benchmark/test_bloatnet.py

Lines changed: 179 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,63 +20,194 @@
2020

2121

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

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

77-
post = {contract_address: Account(storage=storage)}
118+
@pytest.mark.valid_from("Prague")
119+
@pytest.mark.parametrize("final_storage_value", [0x02 << 248, 0x02])
120+
def test_bloatnet_sstore_1_to_2(
121+
blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, gas_benchmark_value: int, final_storage_value: int
122+
):
123+
"""
124+
Benchmark test that maximizes SSTORE operations (1 -> 2).
78125
79-
blockchain_test(pre=pre, blocks=[Block(txs=[tx_0_1]), Block(txs=[tx_1_2])], post=post)
126+
This test pre-fills storage slots with value=1, then overwrites them with value=2.
127+
This represents the case of changing a non-zero value to a different non-zero value,
128+
"""
129+
gas_costs = fork.gas_costs()
130+
intrinsic_gas_calc = fork.transaction_intrinsic_cost_calculator()
131+
tx_gas_cap = fork.transaction_gas_limit_cap() or gas_benchmark_value
132+
133+
initial_value = final_storage_value // 2
134+
calldata = final_storage_value.to_bytes(32, "big").rstrip(b"\x00")
135+
136+
total_sstores = 0
137+
total_block_gas_used = 0
138+
all_txs = []
139+
expected_storage_state = {}
140+
141+
while total_block_gas_used <= gas_benchmark_value:
142+
remaining_block_gas = gas_benchmark_value - total_block_gas_used
143+
tx_gas_limit = min(remaining_block_gas, tx_gas_cap)
144+
145+
intrinsic_gas_with_data_floor = intrinsic_gas_calc(calldata=calldata)
146+
if tx_gas_limit <= intrinsic_gas_with_data_floor:
147+
break
148+
149+
opcode_gas_budget = tx_gas_limit - intrinsic_gas_with_data_floor
150+
151+
# Setup code to load value from calldata
152+
tx_contract_code = Op.PUSH0 + Op.CALLDATALOAD
153+
tx_opcode_gas = gas_costs.G_BASE + gas_costs.G_VERY_LOW # PUSH0 + CALLDATALOAD
154+
155+
sstore_per_op_cost = (
156+
gas_costs.G_VERY_LOW * 2 # PUSH + DUP1
157+
+ gas_costs.G_COLD_SLOAD
158+
+ gas_costs.G_STORAGE_RESET # SSTORE
159+
)
160+
161+
tx_sstores_count = (opcode_gas_budget - tx_opcode_gas) // sstore_per_op_cost
162+
163+
if tx_sstores_count == 0:
164+
break
165+
166+
tx_opcode_gas += sstore_per_op_cost * tx_sstores_count
167+
for slot in range(total_sstores, total_sstores + tx_sstores_count):
168+
tx_contract_code += Op.SSTORE(slot, Op.DUP1)
169+
170+
# Pre-fill storage with initial values
171+
initial_storage = {
172+
slot: initial_value for slot in range(total_sstores, total_sstores + tx_sstores_count)
173+
}
174+
175+
contract_address = pre.deploy_contract(
176+
code=tx_contract_code,
177+
storage=initial_storage, # type: ignore
178+
)
179+
tx = Transaction(
180+
to=contract_address,
181+
gas_limit=tx_gas_limit,
182+
data=calldata,
183+
sender=pre.fund_eoa(),
184+
)
185+
all_txs.append(tx)
186+
187+
actual_intrinsic_consumed = intrinsic_gas_calc(
188+
calldata=calldata, return_cost_deducted_prior_execution=True
189+
)
190+
191+
tx_gas_used = actual_intrinsic_consumed + tx_opcode_gas
192+
total_block_gas_used += tx_gas_used
193+
194+
expected_storage_state[contract_address] = Account(
195+
storage=Storage(
196+
{
197+
HashInt(slot): HashInt(final_storage_value)
198+
for slot in range(total_sstores, total_sstores + tx_sstores_count)
199+
}
200+
)
201+
)
202+
203+
total_sstores += tx_sstores_count
204+
205+
blockchain_test(
206+
pre=pre,
207+
blocks=[Block(txs=all_txs)],
208+
post=expected_storage_state,
209+
expected_benchmark_gas_used=total_block_gas_used,
210+
)
80211

81212

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

0 commit comments

Comments
 (0)