@pytest.mark.parametrize("check_delegated_account_first", [True, False])
@pytest.mark.parametrize(
**gas_test_parameter_args(include_many=False, include_data=False)
)
@pytest.mark.eels_base_coverage
def test_account_warming(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
authorization_list_with_properties: List[AuthorizationWithProperties],
authorization_list: List[AuthorizationTuple],
access_list: List[AccessList],
data: bytes,
sender: EOA,
check_delegated_account_first: bool,
) -> None:
"""
Test warming of the authority and authorized accounts for set-code
transactions.
"""
# Overhead cost is the single push operation required for the address to
# check.
overhead_cost = 3 * len(Op.CALL.kwargs)
gas_costs = fork.gas_costs()
cold_account_cost = gas_costs.COLD_ACCOUNT_ACCESS
warm_account_cost = gas_costs.WARM_ACCESS
access_list_addresses = {
access_list.address for access_list in access_list
}
# Dictionary to keep track of the addresses to check for warming, and the
# expected cost of accessing such account.
addresses_to_check: Dict[Address, int] = {}
for authorization_with_properties in authorization_list_with_properties:
authority = authorization_with_properties.tuple.signer
assert authority is not None, "authority address is not set"
delegated_account = authorization_with_properties.tuple.address
authority_contains_delegation_after_authorization = (
authorization_with_properties.invalidity_type is None
# If the authority already contained a delegation prior to the
# transaction, even if the authorization is invalid, there will be
# a delegation when we check the address.
or authorization_with_properties.authority_type
== AddressType.EOA_WITH_SET_CODE
)
if check_delegated_account_first:
if delegated_account not in addresses_to_check:
addresses_to_check[delegated_account] = (
warm_account_cost
if delegated_account in access_list_addresses
else cold_account_cost
)
if authority not in addresses_to_check:
if not authorization_with_properties.skip:
if (
authorization_with_properties.invalidity_type is None
or (
authorization_with_properties.invalidity_type
!= AuthorizationInvalidityType.INVALID_CHAIN_ID
)
or authority in access_list_addresses
):
access_cost = warm_account_cost
else:
access_cost = cold_account_cost
else:
access_cost = (
cold_account_cost
if Address(sender)
!= authorization_with_properties.tuple.signer
else warm_account_cost
)
if authority_contains_delegation_after_authorization:
# The double charge for accessing the delegated account,
# only if the account ends up with a delegation in its
# code.
access_cost += warm_account_cost
addresses_to_check[authority] = access_cost
else:
if authority not in addresses_to_check:
access_cost = (
cold_account_cost
if Address(sender)
!= authorization_with_properties.tuple.signer
else warm_account_cost
)
if not authorization_with_properties.skip and (
authorization_with_properties.invalidity_type is None
or (
authorization_with_properties.invalidity_type
!= AuthorizationInvalidityType.INVALID_CHAIN_ID
)
or authority in access_list_addresses
):
access_cost = warm_account_cost
if (
# We can only charge the delegated account access cost if
# the authorization went through
authority_contains_delegation_after_authorization
):
if (
delegated_account in addresses_to_check
or delegated_account in access_list_addresses
):
access_cost += warm_account_cost
else:
access_cost += cold_account_cost
addresses_to_check[authority] = access_cost
if delegated_account not in addresses_to_check:
if (
authority_contains_delegation_after_authorization
or delegated_account in access_list_addresses
):
access_cost = warm_account_cost
else:
access_cost = cold_account_cost
addresses_to_check[delegated_account] = access_cost
callee_code: Bytecode = sum( # type: ignore
(
CodeGasMeasure(
code=Op.CALL(gas=0, address=check_address),
overhead_cost=overhead_cost,
extra_stack_items=1,
sstore_key=check_address,
)
for check_address in addresses_to_check
)
)
callee_code += Op.STOP
callee_address = pre.deploy_contract(
callee_code,
storage=dict.fromkeys(addresses_to_check, 0xDEADBEEF),
)
tx = Transaction(
to=callee_address,
authorization_list=authorization_list if authorization_list else None,
access_list=access_list,
sender=sender,
data=data,
)
post = {
callee_address: Account(
storage=addresses_to_check,
),
}
state_test(
pre=pre,
tx=tx,
post=post,
)