@pytest.mark.parametrize(
"value_bearing,beneficiary_is_self",
[
pytest.param(False, False, id="without value"),
pytest.param(True, False, id="with value moved to the creator"),
pytest.param(True, True, id="with value burnt to self"),
],
)
def test_selfdestruct_initcode(
benchmark_test: BenchmarkTestFiller,
pre: Alloc,
value_bearing: bool,
beneficiary_is_self: bool,
fork: Fork,
gas_benchmark_value: int,
) -> None:
"""Benchmark SELFDESTRUCT instruction executed in initcode."""
beneficiary = Op.ADDRESS if beneficiary_is_self else Op.CALLER
initcode = Op.SELFDESTRUCT(beneficiary, address_warm=True)
# CALLDATA[0:32] = iteration_count
setup = (
Op.MSTORE(
0,
initcode.hex(),
# Gas accounting
old_memory_size=0,
new_memory_size=32,
)
+ Op.CALLDATALOAD(0)
+ Op.PUSH0
)
loop = While(
body=Op.POP(
Op.CREATE(
value=1 if value_bearing else 0,
offset=32 - len(initcode),
size=len(initcode),
init_code_size=len(initcode),
)
),
condition=Op.PUSH1(1) + Op.ADD + Op.DUP1 + Op.DUP3 + Op.GT,
)
attack_code = IteratingBytecode(
setup=setup,
iterating=loop,
iterating_subcall=initcode,
cleanup=Op.STOP,
)
def calldata(iteration_count: int, start_iteration: int) -> bytes:
del start_iteration
return Hash(iteration_count)
iteration_counts = list(
attack_code.tx_iterations_by_gas_limit(
fork=fork,
gas_limit=gas_benchmark_value,
calldata=calldata,
)
)
num_iterations = sum(iteration_counts)
attack_code_address = pre.deploy_contract(
code=attack_code,
balance=num_iterations if value_bearing else 0,
)
with TestPhaseManager.execution():
sender = pre.fund_eoa()
exec_txs = list(
attack_code.transactions_by_gas_limit(
fork=fork,
gas_limit=gas_benchmark_value,
sender=sender,
to=attack_code_address,
calldata=calldata,
)
)
total_gas_cost = sum(tx.gas_cost for tx in exec_txs)
returned_to_creator = value_bearing and not beneficiary_is_self
post = {
attack_code_address: Account(
balance=num_iterations if returned_to_creator else 0
)
}
benchmark_test(
post=post,
target_opcode=Op.SELFDESTRUCT,
blocks=[
Block(txs=exec_txs),
],
expected_benchmark_gas_used=total_gas_cost,
)