@pytest.mark.parametrize("opcode", [Op.SHR, Op.SAR])
def test_shifts(
benchmark_test: BenchmarkTestFiller,
pre: Alloc,
fork: Fork,
opcode: Op,
gas_benchmark_value: int,
) -> None:
"""
Benchmark shift instructions with non-trivial arguments.
This generates left-right pairs of shifts to avoid zeroing the argument.
Shift amounts are randomly selected from constant pool of 15 stack values.
"""
max_code_size = fork.max_code_size()
match opcode:
case Op.SHR:
shift_right_fn = shr
case Op.SAR:
shift_right_fn = sar
case _:
raise ValueError(f"Unexpected shift op: {opcode}")
rng = random.Random(1) # Use random with a fixed seed.
initial_value = 2**256 - 1 # The initial value to be shifted; should be
# negative for SAR.
# Create the list of shift amounts with 15 elements (max reachable by DUPs
# instructions). For the worst case keep the values small and omit values
# divisible by 8.
shift_amounts = [x + (x >= 8) + (x >= 15) for x in range(1, 16)]
code_prefix = (
sum(Op.PUSH1[sh] for sh in shift_amounts)
+ Op.JUMPDEST
+ Op.CALLDATALOAD(0)
)
code_suffix = Op.POP + Op.JUMP(len(shift_amounts) * 2)
code_body_len = max_code_size - len(code_prefix) - len(code_suffix)
def select_shift_amount(
shift_fn: Callable[[int, int], int], v: int
) -> tuple[int, int]:
"""Select a shift amount that will produce a non-zero result."""
while True:
index = rng.randint(0, len(shift_amounts) - 1)
sh = shift_amounts[index]
new_v = shift_fn(v, sh) % 2**256
if new_v != 0:
return new_v, index
code_body = Bytecode()
v = initial_value
while len(code_body) <= code_body_len - 4:
v, i = select_shift_amount(shl, v)
code_body += make_dup(len(shift_amounts) - i) + Op.SHL
v, i = select_shift_amount(shift_right_fn, v)
code_body += make_dup(len(shift_amounts) - i) + opcode
code = code_prefix + code_body + code_suffix
assert len(code) == max_code_size - 2
tx = Transaction(
to=pre.deploy_contract(code=code),
data=initial_value.to_bytes(32, byteorder="big"),
gas_limit=gas_benchmark_value,
sender=pre.fund_eoa(),
)
benchmark_test(
target_opcode=opcode,
tx=tx,
)