@pytest.mark.parametrize(
"fixed_src_dst",
[
True,
False,
],
)
@pytest.mark.parametrize(
"calldata_size,non_zero_data",
[
(size, nzd)
for size in [0, 32, 256, 1024]
for nzd in [True, False]
if not (size == 0 and nzd)
],
)
def test_calldatacopy_from_call(
benchmark_test: BenchmarkTestFiller,
fork: Fork,
calldata_size: int,
fixed_src_dst: bool,
non_zero_data: bool,
tx_gas_limit: int,
) -> None:
"""Benchmark CALLDATACOPY instruction."""
# If `non_zero_data` is True, we fill the calldata with deterministic
# random data.
data = (
Bytes([i % 256 for i in range(calldata_size)])
if non_zero_data
else Bytes()
)
intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator()
min_gas = intrinsic_gas_calculator(calldata=data)
if min_gas > tx_gas_limit:
pytest.skip(
f"Intrinsic gas for calldata ({min_gas}) exceeds "
f"tx gas limit ({tx_gas_limit})"
)
# We create the contract that will be doing the CALLDATACOPY multiple
# times.
#
# If `non_zero_data` is True, we leverage CALLDATASIZE for the copy
# length. Otherwise, since we
# don't send zero data explicitly via calldata, PUSH the target size and
# use DUP1 to copy it.
setup = (
Bytecode()
if non_zero_data or calldata_size == 0
else Op.PUSH3(calldata_size)
)
src_dst = 0 if fixed_src_dst else Op.AND(Op.GAS, 7)
attack_block = Op.CALLDATACOPY(
src_dst,
src_dst,
Op.CALLDATASIZE if non_zero_data or calldata_size == 0 else Op.DUP1,
)
benchmark_test(
target_opcode=Op.CALLDATACOPY,
code_generator=ExtCallGenerator(
setup=setup,
attack_block=attack_block,
tx_kwargs={"data": data},
),
)