@pytest.mark.parametrize(
"initcode_name",
[
"max_size_zeros",
"max_size_ones",
"over_limit_zeros",
"over_limit_ones",
"empty",
"single_byte",
"32_bytes",
"33_bytes",
"max_size_minus_word",
"max_size_minus_word_plus_byte",
],
)
@pytest.mark.parametrize("opcode", [Op.CREATE, Op.CREATE2], ids=get_create_id)
class TestCreateInitcode:
"""
Test contract creation with valid and invalid initcode lengths.
Test contract creation via CREATE/CREATE2, parametrized by initcode that is
on/over the max allowed limit.
"""
@pytest.fixture
def create2_salt(self) -> int:
"""
Salt value used for CREATE2 contract creation.
"""
return 0xDEADBEEF
@pytest.fixture
def create_code(
self, opcode: Op, create2_salt: int, initcode: Initcode
) -> Bytecode:
"""
Generate the CREATE/CREATE2 bytecode.
"""
return (
opcode(
size=Op.CALLDATASIZE,
salt=create2_salt,
init_code_size=len(initcode),
)
if opcode == Op.CREATE2
else opcode(size=Op.CALLDATASIZE, init_code_size=len(initcode))
)
@pytest.fixture
def creator_code(self, fork: Fork, create_code: Bytecode) -> Bytecode:
"""
Generate code for the creator contract which calls CREATE/CREATE2.
"""
return (
Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE)
+ Op.GAS
+ create_code
+ Op.GAS
# stack: [Gas 2, Call Result, Gas 1]
+ Op.SWAP1
# stack: [Call Result, Gas 2, Gas 1]
+ Op.PUSH1[0]
# stack: [0, Call Result, Gas 2, Gas 1]
+ Op.SSTORE
# stack: [Gas 2, Gas 1]
+ Op.SWAP1
# stack: [Gas 1, Gas 2]
+ Op.SUB
# stack: [Gas 1 - Gas 2]
+ Op.PUSH1[Op.GAS.gas_cost(fork)]
# stack: [Op.GAS cost, Gas 1 - Gas 2]
+ Op.SWAP1
# stack: [Gas 1 - Gas 2, Op.GAS cost]
+ Op.SUB
# stack: [Gas 1 - Gas 2 - Op.GAS cost]
+ Op.PUSH1[1]
# stack: [1, Gas 1 - Gas 2 - Op.GAS cost]
+ Op.SSTORE
)
@pytest.fixture
def creator_contract_address(
self, pre: Alloc, creator_code: Bytecode
) -> Address:
"""Return address of creator contract."""
return pre.deploy_contract(creator_code, label="creator_contract")
@pytest.fixture
def created_contract_address( # noqa: D103
self,
opcode: Op,
create2_salt: int,
initcode: Initcode,
creator_contract_address: Address,
) -> Address:
"""
Calculate address of the contract created by the creator contract.
"""
return compute_create_address(
address=creator_contract_address,
nonce=1,
salt=create2_salt,
initcode=initcode,
opcode=opcode,
)
@pytest.fixture
def caller_code(self, creator_contract_address: Address) -> Bytecode:
"""
Generate code for the caller contract that calls the creator contract.
"""
return Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + Op.SSTORE(
Op.CALL(
address=creator_contract_address, args_size=Op.CALLDATASIZE
),
1,
)
@pytest.fixture
def caller_contract_address(
self, pre: Alloc, caller_code: Bytecode
) -> Address:
"""Return address of the caller contract."""
return pre.deploy_contract(caller_code, label="caller_contract")
@pytest.fixture
def tx(
self, caller_contract_address: Address, initcode: Initcode, sender: EOA
) -> Transaction:
"""Generate transaction that executes the caller contract."""
return Transaction(
to=caller_contract_address,
data=initcode,
state_gas_reservoir=0, # Don't hide state gas from Op.GAS
sender=sender,
)
@pytest.mark.bigmem
@pytest.mark.xdist_group(name="bigmem")
@pytest.mark.slow()
def test_create_opcode_initcode(
self,
state_test: StateTestFiller,
pre: Alloc,
post: Alloc,
tx: Transaction,
initcode: Initcode,
caller_contract_address: Address,
creator_contract_address: Address,
created_contract_address: Address,
create_code: Bytecode,
fork: Fork,
) -> None:
"""
Test contract creation with valid and invalid initcode lengths.
Test contract creation via CREATE/CREATE2, parametrized by initcode
that is on/over the max allowed limit.
"""
if len(initcode) > fork.max_initcode_size():
# Call returns 0 as out of gas s[0]==1
post[caller_contract_address] = Account(
nonce=1,
storage={
0: 1,
1: 0,
},
)
post[created_contract_address] = Account.NONEXISTENT
post[creator_contract_address] = Account(
nonce=1,
storage={
0: 0,
1: 0,
},
)
else:
expected_gas_usage = create_code.gas_cost(fork)
# The initcode is only executed if the length check succeeds
expected_gas_usage += initcode.gas_cost(fork)
# Call returns 1 as valid initcode length s[0]==1 && s[1]==1
post[caller_contract_address] = Account(
nonce=1,
storage={
0: 0,
1: 1,
},
)
post[created_contract_address] = Account(code=initcode.deploy_code)
post[creator_contract_address] = Account(
nonce=2,
storage={
0: created_contract_address,
1: expected_gas_usage,
},
)
state_test(pre=pre, post=post, tx=tx)