From 94c03adcaf779e4c06c25adecbaf0a9ba5a35627 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 20 Jun 2024 22:49:43 +0000 Subject: [PATCH 01/17] feat(fw): EIP-7251: Add consolidation requests --- src/ethereum_test_tools/__init__.py | 2 + src/ethereum_test_tools/common/__init__.py | 2 + src/ethereum_test_tools/common/types.py | 48 ++++++++++++++++++- .../spec/blockchain/types.py | 25 +++++++++- 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/ethereum_test_tools/__init__.py b/src/ethereum_test_tools/__init__.py index 74025be7a41..d3a2b90dafa 100644 --- a/src/ethereum_test_tools/__init__.py +++ b/src/ethereum_test_tools/__init__.py @@ -19,6 +19,7 @@ Account, Address, Alloc, + ConsolidationRequest, DepositRequest, EngineAPIError, Environment, @@ -79,6 +80,7 @@ "Case", "CodeGasMeasure", "Conditional", + "ConsolidationRequest", "DepositRequest", "EngineAPIError", "Environment", diff --git a/src/ethereum_test_tools/common/__init__.py b/src/ethereum_test_tools/common/__init__.py index 1812f1111bc..6d2dccc7502 100644 --- a/src/ethereum_test_tools/common/__init__.py +++ b/src/ethereum_test_tools/common/__init__.py @@ -40,6 +40,7 @@ AccessList, Account, Alloc, + ConsolidationRequest, DepositRequest, Environment, Removable, @@ -59,6 +60,7 @@ "Alloc", "Bloom", "Bytes", + "ConsolidationRequest", "DepositRequest", "EngineAPIError", "EmptyOmmersRoot", diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index b23949f7f8d..00101889344 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -1547,12 +1547,50 @@ class WithdrawalRequest(WithdrawalRequestGeneric[HexNumber]): pass -class Requests(RootModel[List[DepositRequest | WithdrawalRequest]]): +class ConsolidationRequestGeneric(RequestBase, CamelModel, Generic[NumberBoundTypeVar]): + """ + Generic consolidation request type used as a parent for ConsolidationRequest and + FixtureConsolidationRequest. + """ + + source_address: Address = Address(0) + source_pubkey: BLSPublicKey + target_pubkey: BLSPublicKey + + @classmethod + def type_byte(cls) -> bytes: + """ + Returns the consolidation request type. + """ + return b"\2" + + def to_serializable_list(self) -> List[Any]: + """ + Returns the consolidation's attributes as a list of serializable elements. + """ + return [ + self.source_address, + self.source_pubkey, + self.target_pubkey, + ] + + +class ConsolidationRequest(ConsolidationRequestGeneric[HexNumber]): + """ + Consolidation Request type + """ + + pass + + +class Requests(RootModel[List[DepositRequest | WithdrawalRequest | ConsolidationRequest]]): """ Requests for the transition tool. """ - root: List[DepositRequest | WithdrawalRequest] = Field(default_factory=list) + root: List[DepositRequest | WithdrawalRequest | ConsolidationRequest] = Field( + default_factory=list + ) def to_serializable_list(self) -> List[Any]: """ @@ -1585,6 +1623,12 @@ def withdrawal_requests(self) -> List[WithdrawalRequest]: """ return [w for w in self.root if isinstance(w, WithdrawalRequest)] + def consolidation_requests(self) -> List[ConsolidationRequest]: + """ + Returns the list of consolidation requests. + """ + return [c for c in self.root if isinstance(c, ConsolidationRequest)] + # TODO: Move to other file # Transition tool models diff --git a/src/ethereum_test_tools/spec/blockchain/types.py b/src/ethereum_test_tools/spec/blockchain/types.py index 41dda76238f..c0d124b1213 100644 --- a/src/ethereum_test_tools/spec/blockchain/types.py +++ b/src/ethereum_test_tools/spec/blockchain/types.py @@ -44,6 +44,8 @@ from ...common.types import ( Alloc, CamelModel, + ConsolidationRequest, + ConsolidationRequestGeneric, DepositRequest, DepositRequestGeneric, Environment, @@ -355,7 +357,7 @@ class Block(Header): """ List of withdrawals to perform for this block. """ - requests: List[DepositRequest | WithdrawalRequest] | None = None + requests: List[DepositRequest | WithdrawalRequest | ConsolidationRequest] | None = None """ Custom list of requests to embed in this block. """ @@ -438,6 +440,7 @@ class FixtureExecutionPayload(CamelModel): withdrawals: List[Withdrawal] | None = None deposit_requests: List[DepositRequest] | None = None withdrawal_requests: List[WithdrawalRequest] | None = None + consolidation_requests: List[ConsolidationRequest] | None = None @classmethod def from_fixture_header( @@ -457,6 +460,9 @@ def from_fixture_header( withdrawals=withdrawals, deposit_requests=requests.deposit_requests() if requests is not None else None, withdrawal_requests=requests.withdrawal_requests() if requests is not None else None, + consolidation_requests=requests.consolidation_requests() + if requests is not None + else None, ) @@ -574,6 +580,22 @@ def from_withdrawal_request(cls, d: WithdrawalRequestGeneric) -> "FixtureWithdra return cls(**d.model_dump()) +class FixtureConsolidationRequest(ConsolidationRequestGeneric[ZeroPaddedHexNumber]): + """ + Structure to represent a single consolidation request to be processed by the beacon + chain. + """ + + @classmethod + def from_consolidation_request( + cls, d: ConsolidationRequestGeneric + ) -> "FixtureConsolidationRequest": + """ + Returns a FixtureConsolidationRequest from a ConsolidationRequest. + """ + return cls(**d.model_dump()) + + class FixtureBlockBase(CamelModel): """Representation of an Ethereum block within a test Fixture without RLP bytes.""" @@ -583,6 +605,7 @@ class FixtureBlockBase(CamelModel): withdrawals: List[FixtureWithdrawal] | None = None deposit_requests: List[FixtureDepositRequest] | None = None withdrawal_requests: List[FixtureWithdrawalRequest] | None = None + consolidation_requests: List[FixtureConsolidationRequest] | None = None @computed_field(alias="blocknumber") # type: ignore[misc] @cached_property From 94921fb6d593b2f0167e4f887a9a7738eb08407e Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 20 Jun 2024 22:53:37 +0000 Subject: [PATCH 02/17] feat(fw): EIP-7251: Blockchain-spec, add consolidation requests --- src/ethereum_test_tools/common/types.py | 1 + .../spec/blockchain/blockchain_test.py | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index 00101889344..3c792c78998 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -1703,6 +1703,7 @@ class Result(CamelModel): requests_root: Hash | None = None deposit_requests: List[DepositRequest] | None = None withdrawal_requests: List[WithdrawalRequest] | None = None + consolidation_requests: List[ConsolidationRequest] | None = None class TransitionToolOutput(CamelModel): diff --git a/src/ethereum_test_tools/spec/blockchain/blockchain_test.py b/src/ethereum_test_tools/spec/blockchain/blockchain_test.py index 2cd1366d029..d5d7bd70522 100644 --- a/src/ethereum_test_tools/spec/blockchain/blockchain_test.py +++ b/src/ethereum_test_tools/spec/blockchain/blockchain_test.py @@ -13,7 +13,12 @@ from ...common import Alloc, EmptyTrieRoot, Environment, Hash, Requests, Transaction, Withdrawal from ...common.constants import EmptyOmmersRoot from ...common.json import to_json -from ...common.types import DepositRequest, TransitionToolOutput, WithdrawalRequest +from ...common.types import ( + ConsolidationRequest, + DepositRequest, + TransitionToolOutput, + WithdrawalRequest, +) from ..base.base_test import BaseFixture, BaseTest, verify_result, verify_transactions from ..debugging import print_traces from .types import ( @@ -22,6 +27,7 @@ Fixture, FixtureBlock, FixtureBlockBase, + FixtureConsolidationRequest, FixtureDepositRequest, FixtureEngineNewPayload, FixtureHeader, @@ -152,6 +158,7 @@ def make_genesis( withdrawals=None if env.withdrawals is None else [], deposit_requests=[] if fork.header_requests_required(0, 0) else None, withdrawal_requests=[] if fork.header_requests_required(0, 0) else None, + consolidation_requests=[] if fork.header_requests_required(0, 0) else None, ).with_rlp( txs=[], requests=Requests() if fork.header_requests_required(0, 0) else None ), @@ -259,11 +266,13 @@ def generate_block_data( requests = None if fork.header_requests_required(header.number, header.timestamp): - requests_list: List[DepositRequest | WithdrawalRequest] = [] + requests_list: List[DepositRequest | WithdrawalRequest | ConsolidationRequest] = [] if transition_tool_output.result.deposit_requests is not None: requests_list += transition_tool_output.result.deposit_requests if transition_tool_output.result.withdrawal_requests is not None: requests_list += transition_tool_output.result.withdrawal_requests + if transition_tool_output.result.consolidation_requests is not None: + requests_list += transition_tool_output.result.consolidation_requests requests = Requests(root=requests_list) if requests is not None and requests.trie_root != header.requests_root: @@ -354,6 +363,12 @@ def make_fixture( ] if requests is not None else None, + consolidation_requests=[ + FixtureConsolidationRequest.from_consolidation_request(c) + for c in requests.consolidation_requests() + ] + if requests is not None + else None, ).with_rlp(txs=txs, requests=requests) if block.exception is None: fixture_blocks.append(fixture_block) From 5e8fdafa33c8150fc647461be15347f6e770e6f2 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 20 Jun 2024 23:32:26 +0000 Subject: [PATCH 03/17] new(tests): EIP-7251 condolidation tests --- .../prague/eip7251_consolidations/__init__.py | 3 + .../prague/eip7251_consolidations/conftest.py | 78 ++ .../prague/eip7251_consolidations/helpers.py | 292 +++++++ tests/prague/eip7251_consolidations/spec.py | 86 ++ .../test_withdrawal_requests.py | 742 ++++++++++++++++++ 5 files changed, 1201 insertions(+) create mode 100644 tests/prague/eip7251_consolidations/__init__.py create mode 100644 tests/prague/eip7251_consolidations/conftest.py create mode 100644 tests/prague/eip7251_consolidations/helpers.py create mode 100644 tests/prague/eip7251_consolidations/spec.py create mode 100644 tests/prague/eip7251_consolidations/test_withdrawal_requests.py diff --git a/tests/prague/eip7251_consolidations/__init__.py b/tests/prague/eip7251_consolidations/__init__.py new file mode 100644 index 00000000000..2c845d11939 --- /dev/null +++ b/tests/prague/eip7251_consolidations/__init__.py @@ -0,0 +1,3 @@ +""" +Cross-client EIP-7251 Tests +""" diff --git a/tests/prague/eip7251_consolidations/conftest.py b/tests/prague/eip7251_consolidations/conftest.py new file mode 100644 index 00000000000..133ea2185d6 --- /dev/null +++ b/tests/prague/eip7251_consolidations/conftest.py @@ -0,0 +1,78 @@ +""" +Fixtures for the EIP-7251 consolidations tests. +""" +from typing import List + +import pytest + +from ethereum_test_tools import Alloc, Block, Header + +from .helpers import ConsolidationRequest, ConsolidationRequestInteractionBase +from .spec import Spec + + +@pytest.fixture +def update_pre( + pre: Alloc, + blocks_consolidation_requests: List[List[ConsolidationRequestInteractionBase]], +): + """ + Initial state of the accounts. Every deposit transaction defines their own pre-state + requirements, and this fixture aggregates them all. + """ + for requests in blocks_consolidation_requests: + for r in requests: + r.update_pre(pre) + + +@pytest.fixture +def included_requests( + update_pre: None, # Fixture is used for its side effects + blocks_consolidation_requests: List[List[ConsolidationRequestInteractionBase]], +) -> List[List[ConsolidationRequest]]: + """ + Return the list of consolidation requests that should be included in each block. + """ + excess_withdrawal_requests = 0 + carry_over_requests: List[ConsolidationRequest] = [] + per_block_included_requests: List[List[ConsolidationRequest]] = [] + for block_withdrawal_requests in blocks_consolidation_requests: + # Get fee for the current block + current_minimum_fee = Spec.get_fee(excess_withdrawal_requests) + + # With the fee, get the valid consolidation requests for the current block + current_block_requests = [] + for w in block_withdrawal_requests: + current_block_requests += w.valid_requests(current_minimum_fee) + + # Get the consolidation requests that should be included in the block + pending_requests = carry_over_requests + current_block_requests + per_block_included_requests.append( + pending_requests[: Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK] + ) + carry_over_requests = pending_requests[Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK :] + + # Update the excess consolidation requests + excess_withdrawal_requests = Spec.get_excess_consolidation_requests( + excess_withdrawal_requests, + len(current_block_requests), + ) + return per_block_included_requests + + +@pytest.fixture +def blocks( + update_pre: None, # Fixture is used for its side effects + blocks_consolidation_requests: List[List[ConsolidationRequestInteractionBase]], + included_requests: List[List[ConsolidationRequest]], +) -> List[Block]: + """ + Return the list of blocks that should be included in the test. + """ + return [ + Block( + txs=sum((r.transactions() for r in block_requests), []), + header_verify=Header(requests_root=included_requests[i]), + ) + for i, block_requests in enumerate(blocks_consolidation_requests) + ] diff --git a/tests/prague/eip7251_consolidations/helpers.py b/tests/prague/eip7251_consolidations/helpers.py new file mode 100644 index 00000000000..b11755d465d --- /dev/null +++ b/tests/prague/eip7251_consolidations/helpers.py @@ -0,0 +1,292 @@ +""" +Helpers for the EIP-7251 consolidation tests. +""" +from dataclasses import dataclass, field +from functools import cached_property +from itertools import count +from typing import Callable, ClassVar, List + +from ethereum_test_tools import EOA, Address, Alloc, Bytecode +from ethereum_test_tools import ConsolidationRequest as ConsolidationRequestBase +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import Transaction + +from .spec import Spec + + +class ConsolidationRequest(ConsolidationRequestBase): + """ + Class used to describe a consolidation request in a test. + """ + + fee: int = 0 + """ + Fee to be paid for the consolidation request. + """ + valid: bool = True + """ + Whether the consolidation request is valid or not. + """ + gas_limit: int = 1_000_000 + """ + Gas limit for the call. + """ + calldata_modifier: Callable[[bytes], bytes] = lambda x: x + """ + Calldata modifier function. + """ + + interaction_contract_address: ClassVar[Address] = Address( + Spec.CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS + ) + + @property + def value(self) -> int: + """ + Returns the value of the consolidation request. + """ + return self.fee + + @cached_property + def calldata(self) -> bytes: + """ + Returns the calldata needed to call the consolidation request contract and make the + consolidation. + """ + return self.calldata_modifier(self.source_pubkey + self.target_pubkey) + + def with_source_address(self, source_address: Address) -> "ConsolidationRequest": + """ + Return a new instance of the consolidation request with the source address set. + """ + return self.copy(source_address=source_address) + + +@dataclass(kw_only=True) +class ConsolidationRequestInteractionBase: + """ + Base class for all types of consolidation transactions we want to test. + """ + + sender_balance: int = 32_000_000_000_000_000_000 * 100 + """ + Balance of the account that sends the transaction. + """ + sender_account: EOA | None = None + """ + Account that will send the transaction. + """ + requests: List[ConsolidationRequest] + """ + Withdrawal request to be included in the block. + """ + + def transactions(self) -> List[Transaction]: + """Return a transaction for the consolidation request.""" + raise NotImplementedError + + def update_pre(self, pre: Alloc): + """Return the pre-state of the account.""" + raise NotImplementedError + + def valid_requests(self, current_minimum_fee: int) -> List[ConsolidationRequest]: + """Return the list of consolidation requests that should be valid in the block.""" + raise NotImplementedError + + +@dataclass(kw_only=True) +class ConsolidationRequestTransaction(ConsolidationRequestInteractionBase): + """ + Class used to describe a consolidation request originated from an externally owned account. + """ + + def transactions(self) -> List[Transaction]: + """Return a transaction for the consolidation request.""" + assert self.sender_account is not None, "Sender account not initialized" + return [ + Transaction( + gas_limit=request.gas_limit, + gas_price=0x07, + to=request.interaction_contract_address, + value=request.value, + data=request.calldata, + sender=self.sender_account, + ) + for request in self.requests + ] + + def update_pre(self, pre: Alloc): + """Return the pre-state of the account.""" + self.sender_account = pre.fund_eoa(self.sender_balance) + + def valid_requests(self, current_minimum_fee: int) -> List[ConsolidationRequest]: + """Return the list of consolidation requests that are valid.""" + assert self.sender_account is not None, "Sender account not initialized" + return [ + request.with_source_address(self.sender_account) + for request in self.requests + if request.valid and request.fee >= current_minimum_fee + ] + + +@dataclass(kw_only=True) +class ConsolidationRequestContract(ConsolidationRequestInteractionBase): + """Class used to describe a consolidation originated from a contract.""" + + tx_gas_limit: int = 1_000_000 + """ + Gas limit for the transaction. + """ + + contract_balance: int = 32_000_000_000_000_000_000 * 100 + """ + Balance of the contract that will make the call to the pre-deploy contract. + """ + contract_address: Address | None = None + """ + Address of the contract that will make the call to the pre-deploy contract. + """ + entry_address: Address | None = None + """ + Address to send the transaction to. + """ + + call_type: Op = field(default_factory=lambda: Op.CALL) + """ + Type of call to be used to make the consolidation request. + """ + call_depth: int = 2 + """ + Frame depth of the pre-deploy contract when it executes the call. + """ + extra_code: Bytecode = field(default_factory=Bytecode) + """ + Extra code to be added to the contract code. + """ + + @property + def contract_code(self) -> Bytecode: + """Contract code used by the relay contract.""" + code = Bytecode() + current_offset = 0 + for r in self.requests: + value_arg = [r.value] if self.call_type in (Op.CALL, Op.CALLCODE) else [] + code += Op.CALLDATACOPY(0, current_offset, len(r.calldata)) + Op.POP( + self.call_type( + Op.GAS if r.gas_limit == -1 else r.gas_limit, + r.interaction_contract_address, + *value_arg, + 0, + len(r.calldata), + 0, + 0, + ) + ) + current_offset += len(r.calldata) + return code + self.extra_code + + def transactions(self) -> List[Transaction]: + """Return a transaction for the consolidation request.""" + assert self.entry_address is not None, "Entry address not initialized" + return [ + Transaction( + gas_limit=self.tx_gas_limit, + gas_price=0x07, + to=self.entry_address, + value=0, + data=b"".join(r.calldata for r in self.requests), + sender=self.sender_account, + ) + ] + + def update_pre(self, pre: Alloc): + """Return the pre-state of the account.""" + self.sender_account = pre.fund_eoa(self.sender_balance) + self.contract_address = pre.deploy_contract( + code=self.contract_code, balance=self.contract_balance + ) + self.entry_address = self.contract_address + if self.call_depth > 2: + for _ in range(1, self.call_depth - 1): + self.entry_address = pre.deploy_contract( + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.POP( + Op.CALL( + Op.GAS, + self.entry_address, + 0, + 0, + Op.CALLDATASIZE, + 0, + 0, + ) + ) + ) + + def valid_requests(self, current_minimum_fee: int) -> List[ConsolidationRequest]: + """Return the list of consolidation requests that are valid.""" + assert self.contract_address is not None, "Contract address not initialized" + return [ + r.with_source_address(self.contract_address) + for r in self.requests + if r.valid and r.value >= current_minimum_fee + ] + + +def get_n_fee_increments(n: int) -> List[int]: + """ + Get the first N excess consolidation requests that increase the fee. + """ + excess_consolidation_requests_counts = [] + last_fee = 1 + for i in count(0): + if Spec.get_fee(i) > last_fee: + excess_consolidation_requests_counts.append(i) + last_fee = Spec.get_fee(i) + if len(excess_consolidation_requests_counts) == n: + break + return excess_consolidation_requests_counts + + +def get_n_fee_increment_blocks(n: int) -> List[List[ConsolidationRequestContract]]: + """ + Return N blocks that should be included in the test such that each subsequent block has an + increasing fee for the consolidation requests. + + This is done by calculating the number of consolidations required to reach the next fee + increment and creating a block with that number of consolidation requests plus the number of + consolidations required to reach the target. + """ + blocks = [] + previous_excess = 0 + consolidation_index = 0 + previous_fee = 0 + for required_excess_consolidations in get_n_fee_increments(n): + consolidations_required = ( + required_excess_consolidations + + Spec.TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK + - previous_excess + ) + fee = Spec.get_fee(previous_excess) + assert fee > previous_fee + blocks.append( + [ + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + fee=fee, + ) + for i in range( + consolidation_index, consolidation_index + consolidations_required + ) + ], + ) + ], + ) + previous_fee = fee + consolidation_index += consolidations_required + previous_excess = required_excess_consolidations + + return blocks diff --git a/tests/prague/eip7251_consolidations/spec.py b/tests/prague/eip7251_consolidations/spec.py new file mode 100644 index 00000000000..b7e4e8601d2 --- /dev/null +++ b/tests/prague/eip7251_consolidations/spec.py @@ -0,0 +1,86 @@ +""" +Common procedures to test +[EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://eips.ethereum.org/EIPS/eip-7251) +""" # noqa: E501 + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """ + Defines the reference spec version and git path. + """ + + git_path: str + version: str + + +ref_spec_7251 = ReferenceSpec("EIPS/eip-7251.md", "e5af719767e789c88c0e063406c6557c8f53cfba") + + +# Constants +@dataclass(frozen=True) +class Spec: + """ + Parameters from the EIP-7251 specifications as defined at + https://eips.ethereum.org/EIPS/eip-7251#execution-layer + """ + + CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = 0x00B42DBF2194E931E80326D950320F7D9DBEAC02 + SYSTEM_ADDRESS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE + + EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT = 0 + CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT = 1 + CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT = ( + 2 # Pointer to head of the consolidation request message queue + ) + CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT = ( + 3 # Pointer to the tail of the consolidation request message queue + ) + CONSOLIDATION_REQUEST_QUEUE_STORAGE_OFFSET = ( + 4 # The start memory slot of the in-state consolidation request message queue + ) + MAX_CONSOLIDATION_REQUESTS_PER_BLOCK = ( + 1 # Maximum number of consolidation requests that can be de-queued into a block + ) + TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK = 1 + MIN_CONSOLIDATION_REQUEST_FEE = 1 + CONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION = 17 + EXCESS_INHIBITOR = 1181 + + MAX_AMOUNT = 2**64 - 1 + + @staticmethod + def fake_exponential(factor: int, numerator: int, denominator: int) -> int: + """ + Used to calculate the consolidation request fee. + """ + i = 1 + output = 0 + numerator_accumulator = factor * denominator + while numerator_accumulator > 0: + output += numerator_accumulator + numerator_accumulator = (numerator_accumulator * numerator) // (denominator * i) + i += 1 + return output // denominator + + @staticmethod + def get_fee(excess_consolidation_requests: int) -> int: + """ + Calculate the fee for the excess consolidation requests. + """ + return Spec.fake_exponential( + Spec.MIN_CONSOLIDATION_REQUEST_FEE, + excess_consolidation_requests, + Spec.CONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION, + ) + + @staticmethod + def get_excess_consolidation_requests(previous_excess: int, count: int) -> int: + """ + Calculate the new excess consolidation requests. + """ + if previous_excess + count > Spec.TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK: + return previous_excess + count - Spec.TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK + return 0 diff --git a/tests/prague/eip7251_consolidations/test_withdrawal_requests.py b/tests/prague/eip7251_consolidations/test_withdrawal_requests.py new file mode 100644 index 00000000000..9ca092b923f --- /dev/null +++ b/tests/prague/eip7251_consolidations/test_withdrawal_requests.py @@ -0,0 +1,742 @@ +""" +abstract: Tests [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://eips.ethereum.org/EIPS/eip-7251) + Test execution layer triggered consolidations [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://eips.ethereum.org/EIPS/eip-7251) + +""" # noqa: E501 + +from typing import List + +import pytest + +from ethereum_test_tools import ( + Address, + Alloc, + Block, + BlockchainTestFiller, + BlockException, + Environment, + Header, + Macros, +) +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import TestAddress, TestAddress2 + +from .helpers import ( + ConsolidationRequest, + ConsolidationRequestContract, + ConsolidationRequestInteractionBase, + ConsolidationRequestTransaction, + get_n_fee_increment_blocks, +) +from .spec import Spec, ref_spec_7251 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7251.git_path +REFERENCE_SPEC_VERSION = ref_spec_7251.version + +pytestmark = pytest.mark.valid_from("Prague") + + +@pytest.mark.parametrize( + "blocks_consolidation_requests", + [ + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ) + ], + ), + ], + ], + id="single_block_single_consolidation_request_from_eoa", + ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=0, + ) + ], + ), + ], + ], + id="single_block_single_consolidation_request_from_eoa_insufficient_fee", + ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + calldata_modifier=lambda x: x[:-1], + valid=False, + ) + ], + ), + ], + ], + id="single_block_single_consolidation_request_from_eoa_input_too_short", + ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + calldata_modifier=lambda x: x + b"\x00", + valid=False, + ) + ], + ), + ], + ], + id="single_block_single_consolidation_request_from_eoa_input_too_long", + ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ), + ConsolidationRequest( + source_pubkey=0x03, + target_pubkey=0x04, + fee=Spec.get_fee(0), + ), + ], + ), + ], + ], + id="single_block_multiple_consolidation_request_from_same_eoa", + ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ) + ], + ), + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x03, + target_pubkey=0x04, + fee=Spec.get_fee(0), + ) + ], + ), + ], + ], + id="single_block_multiple_consolidation_request_from_different_eoa", + ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + fee=Spec.get_fee(0), + ) + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK) + ], + ) + ], + ], + id="single_block_max_consolidation_requests_from_eoa", + ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=0, + ), + ConsolidationRequest( + source_pubkey=0x03, + target_pubkey=0x04, + fee=Spec.get_fee(0), + ), + ] + ), + ], + ], + id="single_block_multiple_consolidation_request_first_reverts", + ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ), + ConsolidationRequest( + source_pubkey=0x03, + target_pubkey=0x04, + fee=0, + ), + ] + ), + ], + ], + id="single_block_multiple_consolidation_request_last_reverts", + ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + # TODO: Value obtained from trace minus one + gas_limit=50_000 - 1, + valid=False, + ), + ConsolidationRequest( + source_pubkey=0x03, + target_pubkey=0x04, + fee=Spec.get_fee(0), + ), + ] + ), + ], + ], + id="single_block_multiple_consolidation_request_first_oog", + ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ), + ConsolidationRequest( + source_pubkey=0x03, + target_pubkey=0x04, + fee=Spec.get_fee(0), + # TODO: Value obtained from trace minus one + gas_limit=80_047 - 1, + valid=False, + ), + ] + ), + ], + ], + id="single_block_multiple_consolidation_request_last_oog", + ), + pytest.param( + [ + # Block 1 + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + fee=Spec.get_fee(0), + ) + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 2) + ] + ) + ], + # Block 2, no new consolidation requests, but queued requests from previous block + [], + # Block 3, no new nor queued consolidation requests + [], + ], + id="multiple_block_above_max_consolidation_requests_from_eoa", + ), + pytest.param( + [ + [ + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ), + ] + ), + ], + ], + id="single_block_single_consolidation_request_from_contract", + ), + pytest.param( + [ + [ + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + fee=Spec.get_fee(0), + ) + for i in range( + Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK + ) # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ], + ), + ], + ], + id="single_block_multiple_consolidation_requests_from_contract", + ), + pytest.param( + [ + [ + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=0x00, + target_pubkey=0x01, + fee=0, + ) + ] + + [ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + fee=Spec.get_fee(0), + ) + for i in range( + 1, Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK + ) # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ], + ), + ], + ], + id="single_block_multiple_consolidation_requests_from_contract_first_reverts", + ), + pytest.param( + [ + [ + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + fee=Spec.get_fee(0), + ) + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK - 1) + ] + + [ + ConsolidationRequest( + source_pubkey=-1, + target_pubkey=-2, + fee=0, + ) + ], # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ), + ], + ], + id="single_block_multiple_consolidation_requests_from_contract_last_reverts", + ), + pytest.param( + [ + [ + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=-1, + target_pubkey=-2, + gas_limit=100, + fee=Spec.get_fee(0), + valid=False, + ) + ] + + [ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + gas_limit=1_000_000, + fee=Spec.get_fee(0), + valid=True, + ) + for i in range(1, Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK) + ], + ), # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ], + ], + id="single_block_multiple_consolidation_requests_from_contract_first_oog", + ), + pytest.param( + [ + [ + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + fee=Spec.get_fee(0), + gas_limit=1_000_000, + valid=True, + ) + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK) + ] + + [ + ConsolidationRequest( + source_pubkey=-1, + target_pubkey=-2, + gas_limit=100, + fee=Spec.get_fee(0), + valid=False, + ) + ], + ), # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ], + ], + id="single_block_multiple_consolidation_requests_from_contract_last_oog", + ), + pytest.param( + [ + [ + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + fee=Spec.get_fee(0), + valid=False, + ) + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK) + ], + extra_code=Op.REVERT(0, 0), + ), + ], # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ], + id="single_block_multiple_consolidation_requests_from_contract_caller_reverts", + ), + pytest.param( + [ + [ + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + fee=Spec.get_fee(0), + valid=False, + ) + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK) + ], + extra_code=Macros.OOG(), + ), # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ], + ], + id="single_block_multiple_consolidation_requests_from_contract_caller_oog", + ), + pytest.param( + # Test the first 50 fee increments + get_n_fee_increment_blocks(50), + id="multiple_block_fee_increments", + ), + pytest.param( + [ + [ + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + valid=False, + ) + ], + call_type=Op.DELEGATECALL, + ), + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + valid=False, + ) + ], + call_type=Op.STATICCALL, + ), + ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + valid=False, + ) + ], + call_type=Op.CALLCODE, + ), + ], + ], + id="single_block_single_consolidation_request_delegatecall_staticcall_callcode", + ), + ], +) +def test_consolidation_requests( + blockchain_test: BlockchainTestFiller, + blocks: List[Block], + pre: Alloc, +): + """ + Test making a consolidation request to the beacon chain. + """ + blockchain_test( + genesis_environment=Environment(), + pre=pre, + post={}, + blocks=blocks, + ) + + +@pytest.mark.parametrize( + "requests,block_body_override_requests,exception", + [ + pytest.param( + [], + [ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + source_address=Address(0), + ), + ], + BlockException.INVALID_REQUESTS, + id="no_consolidations_non_empty_requests_list", + ), + pytest.param( + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ), + ] + ), + ], + [], + BlockException.INVALID_REQUESTS, + id="single_consolidation_request_empty_requests_list", + ), + pytest.param( + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ), + ] + ), + ], + [ + ConsolidationRequest( + source_pubkey=0x00, + target_pubkey=0x02, + source_address=TestAddress, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_consolidation_request_source_public_key_mismatch", + ), + pytest.param( + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ), + ] + ), + ], + [ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x00, + source_address=TestAddress, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_consolidation_request_target_public_key_mismatch", + ), + pytest.param( + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ), + ] + ), + ], + [ + ConsolidationRequest( + source_pubkey=0x02, + target_pubkey=0x01, + source_address=TestAddress, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_consolidation_request_pubkeys_swapped", + ), + pytest.param( + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ) + ], + ), + ], + [ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + source_address=TestAddress2, + ) + ], + BlockException.INVALID_REQUESTS, + id="single_consolidation_request_source_address_mismatch", + ), + pytest.param( + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ), + ConsolidationRequest( + source_pubkey=0x03, + target_pubkey=0x04, + fee=Spec.get_fee(0), + ), + ], + ), + ], + [ + ConsolidationRequest( + source_pubkey=0x03, + target_pubkey=0x04, + source_address=TestAddress, + ), + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + source_address=TestAddress, + ), + ], + BlockException.INVALID_REQUESTS, + id="two_consolidation_requests_out_of_order", + ), + pytest.param( + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + fee=Spec.get_fee(0), + ) + ], + ), + ], + [ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + source_address=TestAddress, + ), + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + source_address=TestAddress, + ), + ], + BlockException.INVALID_REQUESTS, + id="single_consolidation_requests_duplicate_in_requests_list", + ), + ], +) +def test_consolidation_requests_negative( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + requests: List[ConsolidationRequestInteractionBase], + block_body_override_requests: List[ConsolidationRequest], + exception: BlockException, +): + """ + Test blocks where the requests list and the actual consolidation requests that happened in the + block's transactions do not match. + """ + for d in requests: + d.update_pre(pre) + + # No previous block so fee is the base + fee = 1 + current_block_requests = [] + for w in requests: + current_block_requests += w.valid_requests(fee) + included_requests = current_block_requests[: Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK] + + blockchain_test( + genesis_environment=Environment(), + pre=pre, + post={}, + blocks=[ + Block( + txs=sum((r.transactions() for r in requests), []), + header_verify=Header( + requests_root=included_requests, + ), + requests=block_body_override_requests, + exception=exception, + ) + ], + ) From 15d56a2c761e67cbb36d640bd0282120358679d5 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 21 Jun 2024 17:31:41 +0000 Subject: [PATCH 04/17] fix(tests): rename consolidations file --- .../{test_withdrawal_requests.py => test_consolidations.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/prague/eip7251_consolidations/{test_withdrawal_requests.py => test_consolidations.py} (100%) diff --git a/tests/prague/eip7251_consolidations/test_withdrawal_requests.py b/tests/prague/eip7251_consolidations/test_consolidations.py similarity index 100% rename from tests/prague/eip7251_consolidations/test_withdrawal_requests.py rename to tests/prague/eip7251_consolidations/test_consolidations.py From 836dbb13318c48f138fb84e3c19e62b2a511f35d Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 21 Jun 2024 18:35:20 +0000 Subject: [PATCH 05/17] fix(fw): Allow negative numbers in `FixedSizeBytes` --- src/ethereum_test_tools/common/conversions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ethereum_test_tools/common/conversions.py b/src/ethereum_test_tools/common/conversions.py index c38e3e2eb40..af33fceea43 100644 --- a/src/ethereum_test_tools/common/conversions.py +++ b/src/ethereum_test_tools/common/conversions.py @@ -59,6 +59,8 @@ def to_fixed_size_bytes(input: FixedSizeBytesConvertible, size: int) -> bytes: Converts multiple types into fixed-size bytes. """ if isinstance(input, int): + if input < 0: + return int.to_bytes(input, length=size, byteorder="big", signed=True) return int.to_bytes(input, length=size, byteorder="big") input = to_bytes(input) if len(input) > size: From 65e9fcd9a560a51abdd4a5b7ccd502f94bcbcd34 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 21 Jun 2024 19:21:57 +0000 Subject: [PATCH 06/17] feat(forks): Add consolidations contract --- .../forks/consolidation_request.bin | Bin 0 -> 328 bytes src/ethereum_test_forks/forks/forks.py | 11 +++++++++++ 2 files changed, 11 insertions(+) create mode 100644 src/ethereum_test_forks/forks/consolidation_request.bin diff --git a/src/ethereum_test_forks/forks/consolidation_request.bin b/src/ethereum_test_forks/forks/consolidation_request.bin new file mode 100644 index 0000000000000000000000000000000000000000..842fd006066c633a5bcd04427f632a122806ef37 GIT binary patch literal 328 zcmZWlJ5Izv48@bwh)79=khY{|KcYeu93YA&J3=n}<_zosCX+pYvoti^0C6`&&Bs|N zkZ|E=S$_6=UOs&NkIx!UVbe6Y4C1B3Lf&};)J>$jfJ!_lJCF)Xt{U8jfpm2H(P=q> zXj0s4Ss%4rogEou1OoLad_w6^(sJmnCF>(#@r$=>o!cn-6Si46pK_5v6<$IMV&P$^ z@fH$xZp`$vDtR@|-|P>-`M%Q;HFX&)>J*;3dGqU*CrXDpQV|rck92BETd`8?EQ?RE ke4dS7gjN`{@|>xPbQRiGB3*CUoM^#f1aq`=_8XCpZ_?^)?EnA( literal 0 HcmV?d00001 diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 4d11dc603f9..45cb74c6de3 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -564,6 +564,17 @@ def pre_allocation_blockchain(cls) -> Mapping: } ) + # Add the consolidation request contract + with open(CURRENT_FOLDER / "consolidation_request.bin", mode="rb") as f: + new_allocation.update( + { + 0x00B42DBF2194E931E80326D950320F7D9DBEAC02: { + "nonce": 1, + "code": f.read(), + }, + } + ) + # Add the history storage contract with open(CURRENT_FOLDER / "contracts" / "history_contract.bin", mode="rb") as f: new_allocation.update( From 1b36346fd5d889dfec1cf5b65aff16e0c95327a1 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 24 Jun 2024 17:14:11 +0000 Subject: [PATCH 07/17] fix(tests): EIP-7251 --- tests/prague/eip7251_consolidations/conftest.py | 12 ++++++------ tests/prague/eip7251_consolidations/helpers.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/prague/eip7251_consolidations/conftest.py b/tests/prague/eip7251_consolidations/conftest.py index 133ea2185d6..60803f1e7c5 100644 --- a/tests/prague/eip7251_consolidations/conftest.py +++ b/tests/prague/eip7251_consolidations/conftest.py @@ -33,16 +33,16 @@ def included_requests( """ Return the list of consolidation requests that should be included in each block. """ - excess_withdrawal_requests = 0 + excess_consolidation_requests = 0 carry_over_requests: List[ConsolidationRequest] = [] per_block_included_requests: List[List[ConsolidationRequest]] = [] - for block_withdrawal_requests in blocks_consolidation_requests: + for block_consolidation_requests in blocks_consolidation_requests: # Get fee for the current block - current_minimum_fee = Spec.get_fee(excess_withdrawal_requests) + current_minimum_fee = Spec.get_fee(excess_consolidation_requests) # With the fee, get the valid consolidation requests for the current block current_block_requests = [] - for w in block_withdrawal_requests: + for w in block_consolidation_requests: current_block_requests += w.valid_requests(current_minimum_fee) # Get the consolidation requests that should be included in the block @@ -53,8 +53,8 @@ def included_requests( carry_over_requests = pending_requests[Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK :] # Update the excess consolidation requests - excess_withdrawal_requests = Spec.get_excess_consolidation_requests( - excess_withdrawal_requests, + excess_consolidation_requests = Spec.get_excess_consolidation_requests( + excess_consolidation_requests, len(current_block_requests), ) return per_block_included_requests diff --git a/tests/prague/eip7251_consolidations/helpers.py b/tests/prague/eip7251_consolidations/helpers.py index b11755d465d..2cde98c364d 100644 --- a/tests/prague/eip7251_consolidations/helpers.py +++ b/tests/prague/eip7251_consolidations/helpers.py @@ -133,7 +133,7 @@ def valid_requests(self, current_minimum_fee: int) -> List[ConsolidationRequest] class ConsolidationRequestContract(ConsolidationRequestInteractionBase): """Class used to describe a consolidation originated from a contract.""" - tx_gas_limit: int = 1_000_000 + tx_gas_limit: int = 10_000_000 """ Gas limit for the transaction. """ From 4b98b89e62f0343ffece3a8906e7b94675d130e7 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 24 Jun 2024 18:29:43 +0000 Subject: [PATCH 08/17] new(tests): Add deposit+withdrawal+consolidation tests --- .../conftest.py | 25 +- ...st_deposits_withdrawals_consolidations.py} | 310 +++++++++--------- whitelist.txt | 1 + 3 files changed, 180 insertions(+), 156 deletions(-) rename tests/prague/eip7685_general_purpose_el_requests/{test_deposits_withdrawals.py => test_deposits_withdrawals_consolidations.py} (54%) diff --git a/tests/prague/eip7685_general_purpose_el_requests/conftest.py b/tests/prague/eip7685_general_purpose_el_requests/conftest.py index fe06e89b2e1..4a4aa61e057 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/conftest.py +++ b/tests/prague/eip7685_general_purpose_el_requests/conftest.py @@ -13,10 +13,16 @@ WithdrawalRequest, WithdrawalRequestInteractionBase, ) +from ..eip7251_consolidations.helpers import ( + ConsolidationRequest, + ConsolidationRequestInteractionBase, +) @pytest.fixture -def block_body_override_requests() -> List[DepositRequest] | None: +def block_body_override_requests() -> List[ + DepositRequest | WithdrawalRequest | ConsolidationRequest +] | None: """List of requests that overwrite the requests in the header. None by default.""" return None @@ -30,27 +36,38 @@ def exception() -> BlockException | None: @pytest.fixture def blocks( pre: Alloc, - requests: List[DepositInteractionBase | WithdrawalRequestInteractionBase], - block_body_override_requests: List[DepositRequest | WithdrawalRequest] | None, + requests: List[ + DepositInteractionBase + | WithdrawalRequestInteractionBase + | ConsolidationRequestInteractionBase + ], + block_body_override_requests: List[DepositRequest | WithdrawalRequest | ConsolidationRequest] + | None, exception: BlockException | None, ) -> List[Block]: """List of blocks that comprise the test.""" included_deposit_requests = [] included_withdrawal_requests = [] + included_consolidation_requests = [] # Single block therefore base fee withdrawal_request_fee = 1 + consolidation_request_fee = 1 for r in requests: r.update_pre(pre) if isinstance(r, DepositInteractionBase): included_deposit_requests += r.valid_requests(10**18) elif isinstance(r, WithdrawalRequestInteractionBase): included_withdrawal_requests += r.valid_requests(withdrawal_request_fee) + elif isinstance(r, ConsolidationRequestInteractionBase): + included_consolidation_requests += r.valid_requests(consolidation_request_fee) return [ Block( txs=sum((r.transactions() for r in requests), []), header_verify=Header( - requests_root=included_deposit_requests + included_withdrawal_requests, + requests_root=included_deposit_requests + + included_withdrawal_requests + + included_consolidation_requests, ), requests=block_body_override_requests, exception=exception, diff --git a/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals.py b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py similarity index 54% rename from tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals.py rename to tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py index ca63e8b9a73..c598a081fe6 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals.py +++ b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py @@ -4,6 +4,7 @@ """ # noqa: E501 +from itertools import permutations from typing import List import pytest @@ -27,6 +28,11 @@ WithdrawalRequestTransaction, ) from ..eip7002_el_triggerable_withdrawals.spec import Spec as Spec_EIP7002 +from ..eip7251_consolidations.helpers import ( + ConsolidationRequest, + ConsolidationRequestContract, + ConsolidationRequestTransaction, +) from .spec import ref_spec_7685 REFERENCE_SPEC_GIT_PATH = ref_spec_7685.git_path @@ -35,183 +41,184 @@ pytestmark = pytest.mark.valid_from("Prague") +def single_deposit_from_eoa(i: int) -> DepositTransaction: # noqa: D103 + return DepositTransaction( + requests=[ + DepositRequest( + pubkey=(i * 3), + withdrawal_credentials=(i * 3) + 1, + amount=32_000_000_000, + signature=(i * 3) + 2, + index=i, + ) + ], + ) + + +def single_deposit_from_contract(i: int) -> DepositContract: # noqa: D103 + return DepositContract( + requests=[ + DepositRequest( + pubkey=(i * 3), + withdrawal_credentials=(i * 3) + 1, + amount=32_000_000_000, + signature=(i * 3) + 2, + index=i, + ) + ], + ) + + +def single_withdrawal_from_eoa(i: int) -> WithdrawalRequestTransaction: # noqa: D103 + return WithdrawalRequestTransaction( + requests=[ + WithdrawalRequest( + validator_pubkey=i + 1, + amount=0, + fee=1, + ) + ], + ) + + +def single_withdrawal_from_contract(i: int) -> WithdrawalRequestContract: # noqa: D103 + return WithdrawalRequestContract( + requests=[ + WithdrawalRequest( + validator_pubkey=i + 1, + amount=0, + fee=1, + ) + ], + ) + + +def single_consolidation_from_eoa(i: int) -> ConsolidationRequestTransaction: # noqa: D103 + return ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=(i * 2), + target_pubkey=(i * 2) + 1, + fee=1, + ) + ], + ) + + +def single_consolidation_from_contract(i: int) -> ConsolidationRequestContract: # noqa: D103 + return ConsolidationRequestContract( + requests=[ + ConsolidationRequest( + source_pubkey=(i * 2), + target_pubkey=(i * 2) + 1, + fee=1, + ) + ], + ) + + +def get_eoa_permutations() -> pytest.param: + """ + Returns all possible permutations of the requests from an EOA. + """ + requests = [ + ( + "deposit_from_eoa", + single_deposit_from_eoa(0), + ), + ( + "withdrawal_from_eoa", + single_withdrawal_from_eoa(0), + ), + ( + "consolidation_from_eoa", + single_consolidation_from_eoa(0), + ), + ] + for perm in permutations(requests, 3): + yield pytest.param([p[1] for p in perm], id="+".join([p[0] for p in perm])) + + +def get_contract_permutations() -> pytest.param: + """ + Returns all possible permutations of the requests from a contract. + """ + requests = [ + ( + "deposit_from_contract", + single_deposit_from_contract(0), + ), + ( + "withdrawal_from_contract", + single_withdrawal_from_contract(0), + ), + ( + "consolidation_from_contract", + single_consolidation_from_contract(0), + ), + ] + for perm in permutations(requests, 3): + yield pytest.param([p[1] for p in perm], id="+".join([p[0] for p in perm])) + + @pytest.mark.parametrize( "requests", [ + *get_eoa_permutations(), + *get_contract_permutations(), pytest.param( [ - DepositTransaction( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ) - ], - ), - WithdrawalRequestTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - fee=1, - ) - ], - ), + single_deposit_from_eoa(0), + single_withdrawal_from_eoa(0), + single_deposit_from_contract(1), ], - id="single_deposit_from_eoa_single_withdrawal_from_eoa", + id="deposit_from_eoa+withdrawal_from_eoa+deposit_from_contract", ), pytest.param( [ - WithdrawalRequestTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - fee=1, - ) - ], - ), - DepositTransaction( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ) - ], - ), + single_withdrawal_from_eoa(0), + single_deposit_from_eoa(0), + single_withdrawal_from_contract(1), ], - id="single_withdrawal_from_eoa_single_deposit_from_eoa", + id="withdrawal_from_eoa+deposit_from_eoa+withdrawal_from_contract", ), pytest.param( [ - DepositTransaction( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ) - ], - ), - WithdrawalRequestTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - fee=1, - ) - ], - ), - DepositTransaction( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x1, - ) - ], - ), + single_deposit_from_eoa(0), + single_consolidation_from_eoa(0), + single_deposit_from_contract(1), ], - id="two_deposits_from_eoa_single_withdrawal_from_eoa", + id="deposit_from_eoa+consolidation_from_eoa+deposit_from_contract", ), pytest.param( [ - WithdrawalRequestTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - fee=1, - ) - ], - ), - DepositTransaction( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ) - ], - ), - WithdrawalRequestTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=1, - fee=1, - ) - ], - ), + single_consolidation_from_eoa(0), + single_deposit_from_eoa(0), + single_consolidation_from_contract(1), ], - id="two_withdrawals_from_eoa_single_deposit_from_eoa", + marks=pytest.mark.skip("Only one consolidation request is allowed per block"), + id="consolidation_from_eoa+deposit_from_eoa+consolidation_from_contract", ), pytest.param( [ - DepositContract( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ) - ], - ), - WithdrawalRequestContract( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - fee=1, - ) - ], - ), + single_consolidation_from_eoa(0), + single_withdrawal_from_eoa(0), + single_consolidation_from_contract(1), ], - id="single_deposit_from_contract_single_withdrawal_from_contract", + marks=pytest.mark.skip("Only one consolidation request is allowed per block"), + id="consolidation_from_eoa+withdrawal_from_eoa+consolidation_from_contract", ), pytest.param( [ - WithdrawalRequestContract( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - fee=1, - ) - ], - ), - DepositContract( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ) - ], - ), + single_withdrawal_from_eoa(0), + single_consolidation_from_eoa(0), + single_withdrawal_from_contract(1), ], - id="single_withdrawal_from_contract_single_deposit_from_contract", + id="withdrawal_from_eoa+consolidation_from_eoa+withdrawal_from_contract", ), - # TODO: Deposit and withdrawal in the same transaction ], ) -def test_valid_deposit_withdrawal_requests( +def test_valid_deposit_withdrawal_consolidation_requests( blockchain_test: BlockchainTestFiller, pre: Alloc, blocks: List[Block], @@ -234,7 +241,7 @@ def test_valid_deposit_withdrawal_requests( pytest.param(False, id="withdrawal_first"), ], ) -def test_valid_deposit_withdrawal_request_from_same_tx( +def test_valid_deposit_withdrawal_consolidation_request_from_same_tx( blockchain_test: BlockchainTestFiller, pre: Alloc, deposit_first: bool, @@ -317,8 +324,7 @@ def test_valid_deposit_withdrawal_request_from_same_tx( withdrawal_request = withdrawal_request.with_source_address(contract_address) tx = Transaction( - gas_limit=1_000_000, - gas_price=0x07, + gas_limit=10_000_000, to=contract_address, value=0, data=calldata, @@ -386,7 +392,7 @@ def test_valid_deposit_withdrawal_request_from_same_tx( ), ], ) -def test_invalid_deposit_withdrawal_requests( +def test_invalid_deposit_withdrawal_consolidation_requests( blockchain_test: BlockchainTestFiller, pre: Alloc, blocks: List[Block], diff --git a/whitelist.txt b/whitelist.txt index 52d4df48894..3364b01ed7c 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -106,6 +106,7 @@ eips EIPs eip6110 eip7002 +eip7251 el endianness EngineAPI From 5498270f51386f702f80d4bbd8d8d4e8e05c4f51 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 24 Jun 2024 19:55:33 +0000 Subject: [PATCH 09/17] new(tests): Add different requests in same tx --- tests/prague/eip6110_deposits/helpers.py | 6 + ...est_deposits_withdrawals_consolidations.py | 253 ++++++++---------- 2 files changed, 119 insertions(+), 140 deletions(-) diff --git a/tests/prague/eip6110_deposits/helpers.py b/tests/prague/eip6110_deposits/helpers.py index 90a05b3c6a4..7d00028aa95 100644 --- a/tests/prague/eip6110_deposits/helpers.py +++ b/tests/prague/eip6110_deposits/helpers.py @@ -91,6 +91,12 @@ def calldata(self) -> bytes: + self.signature ) + def with_source_address(self, source_address: Address) -> "DepositRequest": + """ + Return a copy. + """ + return self.copy() + @dataclass(kw_only=True) class DepositInteractionBase: diff --git a/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py index c598a081fe6..4069edaa9c2 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py +++ b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py @@ -5,20 +5,22 @@ """ # noqa: E501 from itertools import permutations -from typing import List +from typing import Generator, List import pytest from ethereum_test_tools import ( + Account, Alloc, Block, BlockchainTestFiller, BlockException, + Bytecode, Environment, Header, ) from ethereum_test_tools import Opcodes as Op -from ethereum_test_tools import TestAddress, Transaction +from ethereum_test_tools import Storage, TestAddress, Transaction from ..eip6110_deposits.helpers import DepositContract, DepositRequest, DepositTransaction from ..eip6110_deposits.spec import Spec as Spec_EIP6110 @@ -33,6 +35,7 @@ ConsolidationRequestContract, ConsolidationRequestTransaction, ) +from ..eip7251_consolidations.spec import Spec as Spec_EIP7251 from .spec import ref_spec_7685 REFERENCE_SPEC_GIT_PATH = ref_spec_7685.git_path @@ -41,83 +44,79 @@ pytestmark = pytest.mark.valid_from("Prague") -def single_deposit_from_eoa(i: int) -> DepositTransaction: # noqa: D103 - return DepositTransaction( - requests=[ - DepositRequest( - pubkey=(i * 3), - withdrawal_credentials=(i * 3) + 1, - amount=32_000_000_000, - signature=(i * 3) + 2, - index=i, - ) - ], +def single_deposit(i: int) -> DepositRequest: # noqa: D103 + return DepositRequest( + pubkey=(i * 3), + withdrawal_credentials=(i * 3) + 1, + amount=32_000_000_000, + signature=(i * 3) + 2, + index=i, ) +def single_deposit_from_eoa(i: int) -> DepositTransaction: # noqa: D103 + return DepositTransaction(requests=[single_deposit(i)]) + + def single_deposit_from_contract(i: int) -> DepositContract: # noqa: D103 - return DepositContract( - requests=[ - DepositRequest( - pubkey=(i * 3), - withdrawal_credentials=(i * 3) + 1, - amount=32_000_000_000, - signature=(i * 3) + 2, - index=i, - ) - ], + return DepositContract(requests=[single_deposit(i)]) + + +def single_withdrawal(i: int) -> WithdrawalRequest: # noqa: D103 + return WithdrawalRequest( + validator_pubkey=i + 1, + amount=0, + fee=1, ) def single_withdrawal_from_eoa(i: int) -> WithdrawalRequestTransaction: # noqa: D103 - return WithdrawalRequestTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=i + 1, - amount=0, - fee=1, - ) - ], - ) + return WithdrawalRequestTransaction(requests=[single_withdrawal(i)]) def single_withdrawal_from_contract(i: int) -> WithdrawalRequestContract: # noqa: D103 - return WithdrawalRequestContract( - requests=[ - WithdrawalRequest( - validator_pubkey=i + 1, - amount=0, - fee=1, - ) - ], + return WithdrawalRequestContract(requests=[single_withdrawal(i)]) + + +def single_consolidation(i: int) -> ConsolidationRequest: # noqa: D103 + return ConsolidationRequest( + source_pubkey=(i * 2), + target_pubkey=(i * 2) + 1, + fee=1, ) def single_consolidation_from_eoa(i: int) -> ConsolidationRequestTransaction: # noqa: D103 - return ConsolidationRequestTransaction( - requests=[ - ConsolidationRequest( - source_pubkey=(i * 2), - target_pubkey=(i * 2) + 1, - fee=1, - ) - ], - ) + return ConsolidationRequestTransaction(requests=[single_consolidation(i)]) def single_consolidation_from_contract(i: int) -> ConsolidationRequestContract: # noqa: D103 - return ConsolidationRequestContract( - requests=[ - ConsolidationRequest( - source_pubkey=(i * 2), - target_pubkey=(i * 2) + 1, - fee=1, - ) - ], - ) + return ConsolidationRequestContract(requests=[single_consolidation(i)]) + + +def get_permutations(n: int = 3) -> Generator[pytest.param, None, None]: + """ + Returns all possible permutations of the requests from an EOA. + """ + requests = [ + ( + "deposit", + single_deposit(0), + ), + ( + "withdrawal", + single_withdrawal(0), + ), + ( + "consolidation", + single_consolidation(0), + ), + ] + for perm in permutations(requests, n): + yield pytest.param([p[1] for p in perm], id="+".join([p[0] for p in perm])) -def get_eoa_permutations() -> pytest.param: +def get_eoa_permutations(n: int = 3) -> pytest.param: """ Returns all possible permutations of the requests from an EOA. """ @@ -135,11 +134,11 @@ def get_eoa_permutations() -> pytest.param: single_consolidation_from_eoa(0), ), ] - for perm in permutations(requests, 3): + for perm in permutations(requests, n): yield pytest.param([p[1] for p in perm], id="+".join([p[0] for p in perm])) -def get_contract_permutations() -> pytest.param: +def get_contract_permutations(n: int = 3) -> pytest.param: """ Returns all possible permutations of the requests from a contract. """ @@ -157,7 +156,7 @@ def get_contract_permutations() -> pytest.param: single_consolidation_from_contract(0), ), ] - for perm in permutations(requests, 3): + for perm in permutations(requests, n): yield pytest.param([p[1] for p in perm], id="+".join([p[0] for p in perm])) @@ -234,99 +233,66 @@ def test_valid_deposit_withdrawal_consolidation_requests( ) -@pytest.mark.parametrize( - "deposit_first", - [ - pytest.param(True, id="deposit_first"), - pytest.param(False, id="withdrawal_first"), - ], -) +@pytest.mark.parametrize("requests", [*get_permutations()]) def test_valid_deposit_withdrawal_consolidation_request_from_same_tx( blockchain_test: BlockchainTestFiller, pre: Alloc, - deposit_first: bool, + requests: List[DepositRequest | WithdrawalRequest | ConsolidationRequest], ): """ Test making a deposit to the beacon chain deposit contract and a withdrawal in the same tx. """ withdrawal_request_fee = 1 - deposit_request = DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ) - withdrawal_request = WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - ) - if deposit_first: - calldata = deposit_request.calldata + withdrawal_request.calldata - contract_code = ( - Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) - + Op.POP( - Op.CALL( - Op.GAS, - Spec_EIP6110.DEPOSIT_CONTRACT_ADDRESS, - deposit_request.value, - 0, - len(deposit_request.calldata), - 0, - 0, - ) - ) - + Op.POP( - Op.CALL( - Op.GAS, - Spec_EIP7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, - withdrawal_request_fee, - len(deposit_request.calldata), - len(withdrawal_request.calldata), - 0, - 0, - ) - ) - ) - else: - calldata = withdrawal_request.calldata + deposit_request.calldata - contract_code = ( - Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) - + Op.POP( - Op.CALL( - Op.GAS, - Spec_EIP7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, - withdrawal_request_fee, - 0, - len(withdrawal_request.calldata), - 0, - 0, - ) - ) - + Op.POP( - Op.CALL( - Op.GAS, - Spec_EIP6110.DEPOSIT_CONTRACT_ADDRESS, - deposit_request.value, - len(withdrawal_request.calldata), - len(deposit_request.calldata), - 0, - 0, - ) - ) + consolidation_request_fee = 1 + + calldata = b"" + contract_code = Bytecode() + total_value = 0 + storage = Storage() + + for request in requests: + calldata_start = len(calldata) + current_calldata = request.calldata + calldata += current_calldata + + contract_code += Op.CALLDATACOPY(0, calldata_start, len(current_calldata)) + + call_contract_address = 0 + value = 0 + if isinstance(request, DepositRequest): + call_contract_address = Spec_EIP6110.DEPOSIT_CONTRACT_ADDRESS + value = request.value + elif isinstance(request, WithdrawalRequest): + call_contract_address = Spec_EIP7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS + value = withdrawal_request_fee + elif isinstance(request, ConsolidationRequest): + call_contract_address = Spec_EIP7251.CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS + value = consolidation_request_fee + + total_value += value + + contract_code += Op.SSTORE( + storage.store_next(1), + Op.CALL( + Op.GAS, + call_contract_address, + value, + 0, + len(current_calldata), + 0, + 0, + ), ) - sender = pre.fund_eoa(10**18) + sender = pre.fund_eoa() contract_address = pre.deploy_contract( code=contract_code, - balance=deposit_request.value + withdrawal_request_fee, ) - withdrawal_request = withdrawal_request.with_source_address(contract_address) tx = Transaction( gas_limit=10_000_000, to=contract_address, - value=0, + value=total_value, data=calldata, sender=sender, ) @@ -334,12 +300,19 @@ def test_valid_deposit_withdrawal_consolidation_request_from_same_tx( blockchain_test( genesis_environment=Environment(), pre=pre, - post={}, + post={ + contract_address: Account( + storage=storage, + ) + }, blocks=[ Block( txs=[tx], header_verify=Header( - requests_root=[deposit_request, withdrawal_request], + requests_root=[ + request.with_source_address(contract_address) + for request in sorted(requests, key=lambda r: r.type_byte()) + ] ), ) ], From 80e3b7d5a2ebd2cbb86de82fb35643feae1db587 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 24 Jun 2024 20:06:13 +0000 Subject: [PATCH 10/17] new(tests): Add invalid-order requests tests with consolidations --- ...est_deposits_withdrawals_consolidations.py | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py index 4069edaa9c2..11d84599088 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py +++ b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py @@ -20,7 +20,7 @@ Header, ) from ethereum_test_tools import Opcodes as Op -from ethereum_test_tools import Storage, TestAddress, Transaction +from ethereum_test_tools import Storage, TestAddress, TestAddress2, Transaction from ..eip6110_deposits.helpers import DepositContract, DepositRequest, DepositTransaction from ..eip6110_deposits.spec import Spec as Spec_EIP6110 @@ -324,44 +324,42 @@ def test_valid_deposit_withdrawal_consolidation_request_from_same_tx( [ pytest.param( [ - WithdrawalRequestTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - fee=1, - ) - ], - ), - DepositTransaction( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ) - ], - ), + single_withdrawal_from_eoa(0), + single_deposit_from_eoa(0), ], [ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - source_address=TestAddress, - ), - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), + single_withdrawal(0).with_source_address(TestAddress), + single_deposit(0), + ], + # TODO: on the Engine API, the issue should be detected as an invalid block hash + BlockException.INVALID_REQUESTS, + id="single_withdrawal_single_deposit_incorrect_order", + ), + pytest.param( + [ + single_consolidation_from_eoa(0), + single_deposit_from_eoa(0), + ], + [ + single_consolidation(0).with_source_address(TestAddress), + single_deposit(0), + ], + # TODO: on the Engine API, the issue should be detected as an invalid block hash + BlockException.INVALID_REQUESTS, + id="single_consolidation_single_deposit_incorrect_order", + ), + pytest.param( + [ + single_consolidation_from_eoa(0), + single_withdrawal_from_eoa(0), + ], + [ + single_consolidation(0).with_source_address(TestAddress), + single_withdrawal(0).with_source_address(TestAddress2), ], # TODO: on the Engine API, the issue should be detected as an invalid block hash BlockException.INVALID_REQUESTS, - id="single_deposit_from_eoa_single_withdrawal_from_eoa_incorrect_order", + id="single_consolidation_single_withdrawal_incorrect_order", ), ], ) From 480ec0ff569b21bdd60520bea30183faa53b45a2 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 25 Jun 2024 21:00:39 +0000 Subject: [PATCH 11/17] move contract --- .../forks/{ => contracts}/consolidation_request.bin | Bin src/ethereum_test_forks/forks/forks.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/ethereum_test_forks/forks/{ => contracts}/consolidation_request.bin (100%) diff --git a/src/ethereum_test_forks/forks/consolidation_request.bin b/src/ethereum_test_forks/forks/contracts/consolidation_request.bin similarity index 100% rename from src/ethereum_test_forks/forks/consolidation_request.bin rename to src/ethereum_test_forks/forks/contracts/consolidation_request.bin diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 45cb74c6de3..18ce8fa33b6 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -565,7 +565,7 @@ def pre_allocation_blockchain(cls) -> Mapping: ) # Add the consolidation request contract - with open(CURRENT_FOLDER / "consolidation_request.bin", mode="rb") as f: + with open(CURRENT_FOLDER / "contracts" / "consolidation_request.bin", mode="rb") as f: new_allocation.update( { 0x00B42DBF2194E931E80326D950320F7D9DBEAC02: { From 28b086a9127652e7c1fd657dd4c6f72894f31189 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 26 Jun 2024 15:20:29 -0600 Subject: [PATCH 12/17] Update src/ethereum_test_tools/common/conversions.py Co-authored-by: spencer --- src/ethereum_test_tools/common/conversions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ethereum_test_tools/common/conversions.py b/src/ethereum_test_tools/common/conversions.py index af33fceea43..33deb09c3b6 100644 --- a/src/ethereum_test_tools/common/conversions.py +++ b/src/ethereum_test_tools/common/conversions.py @@ -59,9 +59,7 @@ def to_fixed_size_bytes(input: FixedSizeBytesConvertible, size: int) -> bytes: Converts multiple types into fixed-size bytes. """ if isinstance(input, int): - if input < 0: - return int.to_bytes(input, length=size, byteorder="big", signed=True) - return int.to_bytes(input, length=size, byteorder="big") + return int.to_bytes(input, length=size, byteorder="big", signed=input < 0) input = to_bytes(input) if len(input) > size: raise Exception(f"input is too large for fixed size bytes: {len(input)} > {size}") From cfe4683cd59bc6e1414e61393450d8b6f621bbaa Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 26 Jun 2024 15:20:38 -0600 Subject: [PATCH 13/17] Update tests/prague/eip7251_consolidations/helpers.py Co-authored-by: spencer --- tests/prague/eip7251_consolidations/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/prague/eip7251_consolidations/helpers.py b/tests/prague/eip7251_consolidations/helpers.py index 2cde98c364d..8c896b5d7f8 100644 --- a/tests/prague/eip7251_consolidations/helpers.py +++ b/tests/prague/eip7251_consolidations/helpers.py @@ -78,7 +78,7 @@ class ConsolidationRequestInteractionBase: """ requests: List[ConsolidationRequest] """ - Withdrawal request to be included in the block. + Consolidation requests to be included in the block. """ def transactions(self) -> List[Transaction]: From fd5efbe26e59d7bb860c901b8cb8d0a981d4fb97 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 26 Jun 2024 15:20:56 -0600 Subject: [PATCH 14/17] Update tests/prague/eip7251_consolidations/spec.py Co-authored-by: spencer --- tests/prague/eip7251_consolidations/spec.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/prague/eip7251_consolidations/spec.py b/tests/prague/eip7251_consolidations/spec.py index b7e4e8601d2..681f16cc5e0 100644 --- a/tests/prague/eip7251_consolidations/spec.py +++ b/tests/prague/eip7251_consolidations/spec.py @@ -1,7 +1,6 @@ """ -Common procedures to test -[EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://eips.ethereum.org/EIPS/eip-7251) -""" # noqa: E501 +Defines EIP-7251 specification constants and functions. +""" from dataclasses import dataclass From e691b36a18db7fbc7f500185b141230bac1047b3 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 26 Jun 2024 15:21:08 -0600 Subject: [PATCH 15/17] Update tests/prague/eip7251_consolidations/spec.py Co-authored-by: spencer --- tests/prague/eip7251_consolidations/spec.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/prague/eip7251_consolidations/spec.py b/tests/prague/eip7251_consolidations/spec.py index 681f16cc5e0..8bff548334f 100644 --- a/tests/prague/eip7251_consolidations/spec.py +++ b/tests/prague/eip7251_consolidations/spec.py @@ -48,7 +48,6 @@ class Spec: CONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION = 17 EXCESS_INHIBITOR = 1181 - MAX_AMOUNT = 2**64 - 1 @staticmethod def fake_exponential(factor: int, numerator: int, denominator: int) -> int: From 83700706ce175170e463f178cd0abc54a1aa8d31 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 26 Jun 2024 21:52:05 +0000 Subject: [PATCH 16/17] tests: add consolidation test with same pubkey --- .../test_consolidations.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/prague/eip7251_consolidations/test_consolidations.py b/tests/prague/eip7251_consolidations/test_consolidations.py index 9ca092b923f..88aaeb21dbb 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations.py +++ b/tests/prague/eip7251_consolidations/test_consolidations.py @@ -55,6 +55,22 @@ ], id="single_block_single_consolidation_request_from_eoa", ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x01, + fee=Spec.get_fee(0), + ) + ], + ), + ], + ], + id="single_block_single_consolidation_request_from_eoa_equal_pubkeys", + ), pytest.param( [ [ From cf2ba9017cf87b38e44a2a2fe4ba1b2ef8a2a5ab Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 26 Jun 2024 22:27:47 +0000 Subject: [PATCH 17/17] fix(tests): EIP-7002,EIP-7251: keep producing empty blocks to exhaust queues --- .../conftest.py | 22 ++++++-- .../test_withdrawal_requests.py | 6 +- .../prague/eip7251_consolidations/conftest.py | 23 ++++++-- .../test_consolidations.py | 55 +++++++++++-------- ...est_deposits_withdrawals_consolidations.py | 11 ++-- whitelist.txt | 1 + 6 files changed, 76 insertions(+), 42 deletions(-) diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py index 00f8a336c11..4c6cd812b91 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py @@ -1,6 +1,7 @@ """ Fixtures for the EIP-7002 deposit tests. """ +from itertools import zip_longest from typing import List import pytest @@ -57,6 +58,13 @@ def included_requests( excess_withdrawal_requests, len(current_block_requests), ) + while carry_over_requests: + # Keep adding blocks until all withdrawal requests are included + per_block_included_requests.append( + carry_over_requests[: Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK] + ) + carry_over_requests = carry_over_requests[Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK :] + return per_block_included_requests @@ -69,10 +77,16 @@ def blocks( """ Return the list of blocks that should be included in the test. """ - return [ + return [ # type: ignore Block( txs=sum((r.transactions() for r in block_requests), []), - header_verify=Header(requests_root=included_requests[i]), + header_verify=Header(requests_root=block_included_requests), + ) + for block_requests, block_included_requests in zip_longest( + blocks_withdrawal_requests, + included_requests, + fillvalue=[], ) - for i, block_requests in enumerate(blocks_withdrawal_requests) - ] + ] + [ + Block(header_verify=Header(requests_root=[])) + ] # Add an empty block at the end to verify that no more withdrawal requests are included diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py index c11652af793..23d1b3cf963 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py @@ -64,6 +64,7 @@ validator_pubkey=0x01, amount=0, fee=0, + valid=False, ) ], ), @@ -262,7 +263,6 @@ ), pytest.param( [ - # Block 1 [ WithdrawalRequestTransaction( requests=[ @@ -275,10 +275,6 @@ ] ) ], - # Block 2, no new withdrawal requests, but queued requests from previous block - [], - # Block 3, no new nor queued withdrawal requests - [], ], id="multiple_block_above_max_withdrawal_requests_from_eoa", ), diff --git a/tests/prague/eip7251_consolidations/conftest.py b/tests/prague/eip7251_consolidations/conftest.py index 60803f1e7c5..ef5c3d87b9c 100644 --- a/tests/prague/eip7251_consolidations/conftest.py +++ b/tests/prague/eip7251_consolidations/conftest.py @@ -1,6 +1,7 @@ """ Fixtures for the EIP-7251 consolidations tests. """ +from itertools import zip_longest from typing import List import pytest @@ -57,6 +58,14 @@ def included_requests( excess_consolidation_requests, len(current_block_requests), ) + + while carry_over_requests: + # Keep adding blocks until all consolidation requests are included + per_block_included_requests.append( + carry_over_requests[: Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK] + ) + carry_over_requests = carry_over_requests[Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK :] + return per_block_included_requests @@ -69,10 +78,16 @@ def blocks( """ Return the list of blocks that should be included in the test. """ - return [ + return [ # type: ignore Block( txs=sum((r.transactions() for r in block_requests), []), - header_verify=Header(requests_root=included_requests[i]), + header_verify=Header(requests_root=block_included_requests), + ) + for block_requests, block_included_requests in zip_longest( + blocks_consolidation_requests, + included_requests, + fillvalue=[], ) - for i, block_requests in enumerate(blocks_consolidation_requests) - ] + ] + [ + Block(header_verify=Header(requests_root=[])) + ] # Add an empty block at the end to verify that no more consolidation requests are included diff --git a/tests/prague/eip7251_consolidations/test_consolidations.py b/tests/prague/eip7251_consolidations/test_consolidations.py index 88aaeb21dbb..1ce2db47e68 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations.py +++ b/tests/prague/eip7251_consolidations/test_consolidations.py @@ -71,6 +71,22 @@ ], id="single_block_single_consolidation_request_from_eoa_equal_pubkeys", ), + pytest.param( + [ + [ + ConsolidationRequestTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=-1, + target_pubkey=-2, + fee=Spec.get_fee(0), + ) + ], + ), + ], + ], + id="single_block_single_consolidation_request_from_eoa_max_pubkeys", + ), pytest.param( [ [ @@ -80,6 +96,7 @@ source_pubkey=0x01, target_pubkey=0x02, fee=0, + valid=False, ) ], ), @@ -184,6 +201,9 @@ ) ], ], + marks=pytest.mark.skip( + reason="duplicate test due to MAX_CONSOLIDATION_REQUESTS_PER_BLOCK==1" + ), id="single_block_max_consolidation_requests_from_eoa", ), pytest.param( @@ -278,7 +298,6 @@ ), pytest.param( [ - # Block 1 [ ConsolidationRequestTransaction( requests=[ @@ -287,14 +306,10 @@ target_pubkey=i * 2 + 1, fee=Spec.get_fee(0), ) - for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 2) + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5) ] ) ], - # Block 2, no new consolidation requests, but queued requests from previous block - [], - # Block 3, no new nor queued consolidation requests - [], ], id="multiple_block_above_max_consolidation_requests_from_eoa", ), @@ -324,9 +339,7 @@ target_pubkey=i * 2 + 1, fee=Spec.get_fee(0), ) - for i in range( - Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK - ) # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5) ], ), ], @@ -350,9 +363,7 @@ target_pubkey=i * 2 + 1, fee=Spec.get_fee(0), ) - for i in range( - 1, Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK - ) # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + for i in range(1, Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5) ], ), ], @@ -369,7 +380,7 @@ target_pubkey=i * 2 + 1, fee=Spec.get_fee(0), ) - for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK - 1) + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5) ] + [ ConsolidationRequest( @@ -377,7 +388,7 @@ target_pubkey=-2, fee=0, ) - ], # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ], ), ], ], @@ -404,9 +415,9 @@ fee=Spec.get_fee(0), valid=True, ) - for i in range(1, Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK) + for i in range(1, Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5) ], - ), # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ), ], ], id="single_block_multiple_consolidation_requests_from_contract_first_oog", @@ -423,7 +434,7 @@ gas_limit=1_000_000, valid=True, ) - for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK) + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5) ] + [ ConsolidationRequest( @@ -434,7 +445,7 @@ valid=False, ) ], - ), # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ), ], ], id="single_block_multiple_consolidation_requests_from_contract_last_oog", @@ -450,11 +461,11 @@ fee=Spec.get_fee(0), valid=False, ) - for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK) + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5) ], extra_code=Op.REVERT(0, 0), ), - ], # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ], ], id="single_block_multiple_consolidation_requests_from_contract_caller_reverts", ), @@ -469,10 +480,10 @@ fee=Spec.get_fee(0), valid=False, ) - for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK) + for i in range(Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5) ], extra_code=Macros.OOG(), - ), # TODO: MAX_CONSOLIDATION_REQUESTS_PER_BLOCK not ideal + ), ], ], id="single_block_multiple_consolidation_requests_from_contract_caller_oog", diff --git a/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py index 11d84599088..e455e083061 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py +++ b/tests/prague/eip7685_general_purpose_el_requests/test_deposits_withdrawals_consolidations.py @@ -274,13 +274,10 @@ def test_valid_deposit_withdrawal_consolidation_request_from_same_tx( contract_code += Op.SSTORE( storage.store_next(1), Op.CALL( - Op.GAS, - call_contract_address, - value, - 0, - len(current_calldata), - 0, - 0, + address=call_contract_address, + value=value, + args_offset=0, + args_size=len(current_calldata), ), ) diff --git a/whitelist.txt b/whitelist.txt index 3364b01ed7c..0b59b02ad8a 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -134,6 +134,7 @@ extcodehash extcodesize F00 filesystem +fillvalue firstlineno fn fname