Skip to content

Commit 3c7d208

Browse files
feat: add extra sstore benchmark cases
1 parent f48b0bd commit 3c7d208

File tree

1 file changed

+192
-0
lines changed

1 file changed

+192
-0
lines changed

tests/benchmark/compute/instruction/test_storage.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@
1010

1111
import pytest
1212
from execution_testing import (
13+
AccessList,
14+
Account,
1315
Alloc,
1416
BenchmarkTestFiller,
1517
Block,
1618
Bytecode,
1719
Environment,
1820
Fork,
21+
Hash,
1922
JumpLoopGenerator,
2023
Op,
24+
Storage,
2125
TestPhaseManager,
2226
Transaction,
2327
While,
@@ -364,3 +368,191 @@ def test_storage_access_warm(
364368
blocks.append(Block(txs=[op_tx]))
365369

366370
benchmark_test(blocks=blocks)
371+
372+
373+
def storage_contract(sloads_before_sstore: bool) -> Bytecode:
374+
"""
375+
Storage contract for benchmark slot access.
376+
377+
# Calldata Layout:
378+
# - CALLDATA[0..31]: Number of slots to access
379+
# - CALLDATA[32..63]: Starting slot index
380+
# - CALLDATA[64..95]: Value to write
381+
"""
382+
setup = Bytecode()
383+
loop = Bytecode()
384+
cleanup = Bytecode()
385+
386+
start_marker = 10
387+
end_marker = 30 + (2 if sloads_before_sstore else 0)
388+
389+
setup += (
390+
Op.CALLDATALOAD(0) # num_slots
391+
+ Op.CALLDATALOAD(32) # start_slot
392+
+ Op.CALLDATALOAD(64) # value
393+
)
394+
395+
setup += Op.PUSH0 # Counter
396+
setup += Op.JUMPDEST
397+
# [counter, value, start_slot, num_slots]
398+
399+
# Loop Condition: Counter < Num Slots
400+
loop += Op.DUP4
401+
loop += Op.DUP2
402+
loop += Op.LT
403+
loop += Op.ISZERO
404+
loop += Op.PUSH1(end_marker)
405+
loop += Op.JUMPI
406+
# [counter, value, start_slot, num_slots]
407+
408+
# Loop Body: Store Value at Start Slot + Counter
409+
loop += Op.DUP1
410+
loop += Op.DUP4
411+
loop += Op.ADD
412+
loop += Op.DUP3
413+
# [value, start_slot+counter, counter, value, start_slot, num_slots]
414+
415+
if sloads_before_sstore:
416+
loop += Op.DUP2
417+
loop += Op.SSTORE
418+
loop += Op.SLOAD
419+
loop += Op.POP
420+
else:
421+
loop += Op.SWAP1
422+
loop += Op.SSTORE # STORAGE[start_slot + counter] = value
423+
# [counter, value, start_slot, num_slots]
424+
425+
# Loop Post: Increment Counter
426+
loop += Op.PUSH1(1)
427+
loop += Op.ADD
428+
loop += Op.PUSH1(start_marker)
429+
loop += Op.JUMP
430+
# [counter + 1, value, start_slot, num_slots]
431+
432+
# Cleanup: Stop
433+
cleanup += Op.JUMPDEST
434+
cleanup += Op.STOP
435+
436+
assert len(setup) - 1 == start_marker
437+
assert len(setup) + len(loop) == end_marker
438+
print(f"setup: {len(setup)}, loop: {len(loop)}, cleanup: {len(cleanup)}")
439+
return setup + loop + cleanup
440+
441+
442+
@pytest.mark.parametrize("slot_count", [50, 100])
443+
@pytest.mark.parametrize("use_access_list", [True, False])
444+
@pytest.mark.parametrize(
445+
"contract_size",
446+
[
447+
pytest.param(0, id="just_created"),
448+
pytest.param(1024, id="small"),
449+
pytest.param(12 * 1024, id="medium"),
450+
pytest.param(24 * 1024, id="xen"),
451+
],
452+
)
453+
@pytest.mark.parametrize("sloads_before_sstore", [True, False])
454+
@pytest.mark.parametrize("num_contracts", [1, 5, 10])
455+
@pytest.mark.parametrize(
456+
"initial_value,write_value",
457+
[
458+
pytest.param(0, 0, id="zero_to_zero"),
459+
pytest.param(0, 0xDEADBEEF, id="zero_to_nonzero"),
460+
pytest.param(0xDEADBEEF, 0, id="nonzero_to_zero"),
461+
pytest.param(0xDEADBEEF, 0xBEEFBEEF, id="nonzero_to_nonzero"),
462+
],
463+
)
464+
def test_sstore_variants(
465+
benchmark_test: BenchmarkTestFiller,
466+
pre: Alloc,
467+
gas_benchmark_value: int,
468+
slot_count: int,
469+
use_access_list: bool,
470+
contract_size: int,
471+
sloads_before_sstore: bool,
472+
num_contracts: int,
473+
initial_value: int,
474+
write_value: int,
475+
) -> None:
476+
"""
477+
Benchmark SSTORE instruction with various configurations.
478+
479+
Variants:
480+
- use_access_list: Warm storage slots via access list
481+
- contract_size: Contract code size
482+
(just_created=0, small=1KB, medium=12KB, xen=24KB)
483+
- sloads_before_sstore: Number of SLOADs per slot before SSTORE
484+
- num_contracts: Number of contract instances (cold storage writes)
485+
- initial_value/write_value: Storage transitions
486+
(zero_to_zero, zero_to_nonzero, nonzero_to_zero, nonzero_to_nonzero)
487+
"""
488+
base_contract = storage_contract(sloads_before_sstore)
489+
padded_contract = base_contract
490+
491+
if len(base_contract) < contract_size:
492+
padded_contract += Op.INVALID * (contract_size - len(base_contract))
493+
494+
slots_per_contract = slot_count // num_contracts
495+
496+
txs = []
497+
post = {}
498+
499+
base_gas_per_contract = gas_benchmark_value // num_contracts
500+
gas_remainder = gas_benchmark_value % num_contracts
501+
502+
for contract_idx in range(num_contracts):
503+
initial_storage = Storage()
504+
505+
start_slot = contract_idx * slot_count
506+
for i in range(slots_per_contract):
507+
initial_storage[start_slot + i] = initial_value
508+
509+
contract_addr = pre.deploy_contract(
510+
code=padded_contract,
511+
storage=initial_storage,
512+
)
513+
514+
calldata = (
515+
slots_per_contract.to_bytes(32, "big")
516+
+ start_slot.to_bytes(32, "big")
517+
+ write_value.to_bytes(32, "big")
518+
)
519+
520+
access_list = None
521+
if use_access_list:
522+
storage_keys = [
523+
Hash(start_slot + i) for i in range(slots_per_contract)
524+
]
525+
access_list = [
526+
AccessList(
527+
address=contract_addr,
528+
storage_keys=storage_keys,
529+
)
530+
]
531+
532+
contract_gas_limit = base_gas_per_contract
533+
if contract_idx == 0:
534+
contract_gas_limit += gas_remainder
535+
536+
tx = Transaction(
537+
to=contract_addr,
538+
data=calldata,
539+
gas_limit=contract_gas_limit,
540+
sender=pre.fund_eoa(),
541+
access_list=access_list,
542+
)
543+
txs.append(tx)
544+
545+
expected_storage = Storage()
546+
for i in range(slots_per_contract):
547+
expected_storage[start_slot + i] = write_value
548+
549+
post[contract_addr] = Account(
550+
code=padded_contract,
551+
storage=expected_storage,
552+
)
553+
554+
benchmark_test(
555+
blocks=[Block(txs=txs)],
556+
post=post,
557+
skip_gas_used_validation=True,
558+
)

0 commit comments

Comments
 (0)