Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a150f8e
new(tests): Add EIP-6110 Sepolia Variant Contract test
marioevz Mar 5, 2025
52d4ffc
fix(tests): Log code
marioevz Mar 5, 2025
4b32375
fix(tests): Log code
marioevz Mar 5, 2025
ac1a0d1
fix: ABI encoding optional
marioevz Mar 6, 2025
bdc6d17
fix: parametrize for ABI (No-ABI test disabled)
marioevz Mar 6, 2025
badf7cf
fix(docs): add docs to helper
jochem-brouwer Mar 31, 2025
2c6443c
fix(tests): lint modified_contract (?)
jochem-brouwer Mar 31, 2025
98f2dd9
feat(tests): add DEPOSIT_EVENT_SIGNATURE_HASH EIP-6110
jochem-brouwer Mar 31, 2025
aec74d1
tmp: add sepolia deposit log data
jochem-brouwer Mar 31, 2025
46e315d
change test and helper methods
jochem-brouwer Apr 3, 2025
1ca916f
convert back to little-endian encoding
jochem-brouwer Apr 3, 2025
d4be5db
feat(helpers): add helper method to construct modified deposit logs
jochem-brouwer Apr 8, 2025
2a85c2f
feat(tests): use new helper in modified contract tests
jochem-brouwer Apr 8, 2025
bafdb21
update test modified contract
jochem-brouwer Apr 10, 2025
d205135
fix test filler to produce the correct requests hash
jochem-brouwer Apr 15, 2025
c23faf9
feat(tests): add EIP6110 invalid event layout tests
jochem-brouwer Apr 15, 2025
5a3e363
feat(tests): add exception to transaction
jochem-brouwer Apr 16, 2025
c50ecca
cleanup(tests): use DEFAULTs where appropriate
jochem-brouwer Apr 16, 2025
ea65f51
feat(tests): add invalid log length test
jochem-brouwer Apr 16, 2025
9512f65
chore(tests) lint
jochem-brouwer Apr 16, 2025
9b0f133
Update tests/prague/eip6110_deposits/helpers.py
jochem-brouwer Apr 16, 2025
ad1e86f
Update tests/prague/eip6110_deposits/helpers.py
jochem-brouwer Apr 16, 2025
acf8529
Update tests/prague/eip6110_deposits/test_modified_contract.py
jochem-brouwer Apr 16, 2025
05a4ae8
Update tests/prague/eip6110_deposits/test_modified_contract.py
jochem-brouwer Apr 16, 2025
1f6ee64
Update tests/prague/eip6110_deposits/helpers.py
jochem-brouwer Apr 16, 2025
84dd1da
Update tests/prague/eip6110_deposits/helpers.py
jochem-brouwer Apr 16, 2025
a9273da
chore(tests): lint
jochem-brouwer Apr 16, 2025
ee91ad7
chore(tests): make typecheck say "congratulations"
jochem-brouwer Apr 16, 2025
c8fa0ec
chore(tests): add pytestmark
jochem-brouwer Apr 16, 2025
4f5cc55
chore(tests): remove TODO
jochem-brouwer Apr 16, 2025
bbc589a
Update tests/prague/eip6110_deposits/test_modified_contract.py
marioevz Apr 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/ethereum_test_exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,11 @@ class TransactionException(ExceptionBase):
"""
Transaction type 4 included before activation fork.
"""
INVALID_DEPOSIT_EVENT_LAYOUT = auto()
"""
Transaction emits a `DepositEvent` in the deposit contract (EIP-6110), but the layout
of the event does not match the required layout.
"""


@unique
Expand Down
95 changes: 95 additions & 0 deletions tests/prague/eip6110_deposits/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,62 @@ def sha256(*args: bytes) -> bytes:
return sha256_hashlib(b"".join(args)).digest()


def create_deposit_log_bytes(
pubkey_size: int = 48,
pubkey_data: bytes = b"",
pubkey_offset: int = 160,
withdrawal_credentials_size: int = 32,
withdrawal_credentials_data: bytes = b"",
withdrawal_credentials_offset: int = 256,
amount_size: int = 8,
amount_data: bytes = b"",
amount_offset: int = 320,
signature_size: int = 96,
signature_data: bytes = b"",
signature_offset: int = 384,
index_size: int = 8,
index_data: bytes = b"",
index_offset: int = 512,
) -> bytes:
"""Create the deposit log bytes."""
result = bytearray(576)
offset = 0

def write_uint256(value: int):
nonlocal offset
result[offset : offset + 32] = value.to_bytes(32, byteorder="big")
offset += 32

def write_bytes(data: bytes, size: int):
nonlocal offset
padded = data.ljust(size, b"\x00")
result[offset : offset + size] = padded
offset += size

write_uint256(pubkey_offset)
write_uint256(withdrawal_credentials_offset)
write_uint256(amount_offset)
write_uint256(signature_offset)
write_uint256(index_offset)

write_uint256(pubkey_size)
write_bytes(pubkey_data, 64)

write_uint256(withdrawal_credentials_size)
write_bytes(withdrawal_credentials_data, 32)

write_uint256(amount_size)
write_bytes(amount_data, 32)

write_uint256(signature_size)
write_bytes(signature_data, 96)

write_uint256(index_size)
write_bytes(index_data, 32)

return bytes(result)


class DepositRequest(DepositRequestBase):
"""Deposit request descriptor."""

Expand Down Expand Up @@ -94,6 +150,45 @@ def calldata(self) -> bytes:
+ self.signature
)

def log(self, *, include_abi_encoding: bool = True) -> bytes:
"""
Return the log data for the deposit event.

event DepositEvent(
bytes pubkey,
bytes withdrawal_credentials,
bytes amount,
bytes signature,
bytes index
);
"""
data = bytearray(576)
if include_abi_encoding:
# Insert ABI encoding
data[30:32] = b"\x00\xa0" # Offset: pubkey (160)
data[62:64] = b"\x01\x00" # Offset: withdrawal_credentials (256)
data[94:96] = b"\x01\x40" # Offset: amount (320)
data[126:128] = b"\x01\x80" # Offset: signature (384)
data[158:160] = b"\x02\x00" # Offset: index (512)
data[190:192] = b"\x00\x30" # Size: pubkey (48)
data[286:288] = b"\x00\x20" # Size: withdrawal_credentials (32)
data[350:352] = b"\x00\x08" # Size: amount (8)
data[414:416] = b"\x00\x60" # Size: signature (96)
data[542:544] = b"\x00\x08" # Size: index (8)
offset = 192
data[offset : offset + len(self.pubkey)] = self.pubkey # [192:240]
offset += 48 + len(self.pubkey)
data[offset : offset + len(self.withdrawal_credentials)] = (
self.withdrawal_credentials
) # [288:320]
offset += 32 + len(self.withdrawal_credentials)
data[offset : offset + 8] = (self.amount).to_bytes(8, byteorder="little") # [352:360]
offset += 56 + 8
data[offset : offset + len(self.signature)] = self.signature # [416:512]
offset += 32 + len(self.signature)
data[offset : offset + 8] = (self.index).to_bytes(8, byteorder="little") # [544:552]
return bytes(data)

def with_source_address(self, source_address: Address) -> "DepositRequest":
"""Return a copy."""
return self.copy()
Expand Down
5 changes: 4 additions & 1 deletion tests/prague/eip6110_deposits/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,7 @@ class Spec:
https://eips.ethereum.org/EIPS/eip-6110.
"""

DEPOSIT_CONTRACT_ADDRESS = 0x00000000219AB540356CBB839CBE05303D7705FA
DEPOSIT_CONTRACT_ADDRESS = 0x00000000219AB540356CBB839CBE05303D7705FA # Mainnet
DEPOSIT_EVENT_SIGNATURE_HASH = (
0x649BBC62D0E31342AFEA4E5CD82D4049E7E1EE912FC0889AA790803BE39038C5
)
223 changes: 223 additions & 0 deletions tests/prague/eip6110_deposits/test_modified_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
"""Test variants of the deposit contract which adheres the log-style as described in EIP-6110."""

import pytest

from ethereum_test_exceptions.exceptions import TransactionException
from ethereum_test_tools import (
Account,
Alloc,
Block,
BlockchainTestFiller,
Header,
Requests,
Transaction,
)
from ethereum_test_tools import Macros as Om
from ethereum_test_tools import Opcodes as Op

from .helpers import DepositRequest, create_deposit_log_bytes
from .spec import Spec, ref_spec_6110

pytestmark = pytest.mark.valid_from("Prague")

REFERENCE_SPEC_GIT_PATH = ref_spec_6110.git_path
REFERENCE_SPEC_VERSION = ref_spec_6110.version

EVENT_ARGUMENTS_NAMES = ["pubkey", "withdrawal_credentials", "amount", "signature", "index"]
EVENT_ARGUMENTS_LAYOUT_TYPE = ["size", "offset"]
EVENT_ARGUMENTS = [
f"{name}_{layout}" for name in EVENT_ARGUMENTS_NAMES for layout in EVENT_ARGUMENTS_LAYOUT_TYPE
]
EVENT_ARGUMENT_VALUES = ["zero", "max_uint256"]


DEFAULT_DEPOSIT_REQUEST = DepositRequest(
pubkey=0x01,
withdrawal_credentials=0x02,
amount=120_000_000_000_000_000,
signature=0x03,
index=0x0,
)
DEFAULT_DEPOSIT_REQUEST_LOG_DATA_DICT = {
"pubkey_data": bytes(DEFAULT_DEPOSIT_REQUEST.pubkey),
"withdrawal_credentials_data": bytes(DEFAULT_DEPOSIT_REQUEST.withdrawal_credentials),
# Note: after converting to bytes, it is converted to little-endian by `[::-1]`
# (This happens on-chain also, but this is done by the solidity contract)
"amount_data": bytes.fromhex("0" + DEFAULT_DEPOSIT_REQUEST.amount.hex()[2:])[::-1],
"signature_data": bytes(DEFAULT_DEPOSIT_REQUEST.signature),
"index_data": bytes(DEFAULT_DEPOSIT_REQUEST.index),
}
DEFAULT_REQUEST_LOG = create_deposit_log_bytes(**DEFAULT_DEPOSIT_REQUEST_LOG_DATA_DICT) # type: ignore


@pytest.mark.parametrize(
"include_deposit_event",
[
pytest.param(True),
pytest.param(False),
],
)
def test_extra_logs(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
include_deposit_event: bool,
):
"""Test deposit contract emitting more log event types than the ones in mainnet."""
# Supplant mainnet contract with a variant that emits a `Transfer`` log
# If `include_deposit_event` is `True``, it will also emit a `DepositEvent` log`

# ERC20 token transfer log (Sepolia)
# https://sepolia.etherscan.io/tx/0x2d71f3085a796a0539c9cc28acd9073a67cf862260a41475f000dd101279f94f
# JSON RPC:
# curl https://sepolia.infura.io/v3/APIKEY \
# -X POST \
# -H "Content-Type: application/json" \
# -d '{"jsonrpc": "2.0", "method": "eth_getLogs",
# "params": [{"address": "0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D",
# "blockHash": "0x8062a17fa791f5dbd59ea68891422e3299ca4e80885a89acf3fc706c8bceef53"}],
# "id": 1}'

# {"jsonrpc":"2.0","id":1,"result":
# [{"removed":false,"logIndex":"0x80","transactionIndex":"0x56",
# "transactionHash":"0x2d71f3085a796a0539c9cc28acd9073a67cf862260a41475f000dd101279f94f",
# "blockHash":"0x8062a17fa791f5dbd59ea68891422e3299ca4e80885a89acf3fc706c8bceef53",
# "blockNumber":"0x794fb5",
# "address":"0x7f02c3e3c98b133055b8b348b2ac625669ed295d",
# "data":"0x0000000000000000000000000000000000000000000000000000000000000001",
# "topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
# "0x0000000000000000000000006885e36bfcb68cb383dfe90023a462c03bcb2ae5",
# "0x00000000000000000000000080b5dc88c98e528bf9cb4b7f0f076ac41da24651"]

bytecode = Op.LOG3(
# ERC-20 token transfer log
# ERC-20 token transfers are LOG3, since the topic, the sender, and receiver
# are all topics (the sender and receiver are `indexed` in the solidity event)
0,
32,
0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF,
0x000000000000000000000000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,
0x000000000000000000000000BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB,
)

requests = Requests()

if include_deposit_event:
bytecode += Om.MSTORE(DEFAULT_REQUEST_LOG) + Op.LOG1(
0,
len(DEFAULT_REQUEST_LOG),
Spec.DEPOSIT_EVENT_SIGNATURE_HASH,
)
requests = Requests(DEFAULT_DEPOSIT_REQUEST)
bytecode += Op.STOP

pre[Spec.DEPOSIT_CONTRACT_ADDRESS] = Account(
code=bytecode,
nonce=1,
balance=0,
)
sender = pre.fund_eoa()

tx = Transaction(
to=Spec.DEPOSIT_CONTRACT_ADDRESS,
sender=sender,
gas_limit=100_000,
)

blockchain_test(
pre=pre,
blocks=[
Block(
txs=[tx],
header_verify=Header(
requests_hash=requests,
),
),
],
post={},
)


@pytest.mark.parametrize(
"log_argument,value",
[(log_argument, value) for log_argument in EVENT_ARGUMENTS for value in EVENT_ARGUMENT_VALUES],
)
@pytest.mark.exception_test
def test_invalid_layout(
blockchain_test: BlockchainTestFiller, pre: Alloc, log_argument: str, value: str
):
"""Test deposit contract emitting logs with invalid layouts (sizes/offsets)."""
log_params = {**DEFAULT_DEPOSIT_REQUEST_LOG_DATA_DICT}
log_params[log_argument] = 0 if value == "zero" else 2**256 - 1 # type: ignore

deposit_request_log = create_deposit_log_bytes(**log_params) # type: ignore

bytecode = Om.MSTORE(deposit_request_log) + Op.LOG1(
0,
len(deposit_request_log),
Spec.DEPOSIT_EVENT_SIGNATURE_HASH,
)
bytecode += Op.STOP

pre[Spec.DEPOSIT_CONTRACT_ADDRESS] = Account(
code=bytecode,
nonce=1,
balance=0,
)
sender = pre.fund_eoa()

tx = Transaction(
to=Spec.DEPOSIT_CONTRACT_ADDRESS,
sender=sender,
gas_limit=100_000,
error=TransactionException.INVALID_DEPOSIT_EVENT_LAYOUT,
)

blockchain_test(
pre=pre,
blocks=[
Block(txs=[tx], exception=TransactionException.INVALID_DEPOSIT_EVENT_LAYOUT),
],
post={},
)


@pytest.mark.parametrize(
"slice_bytes",
[
pytest.param(True),
pytest.param(False),
],
)
@pytest.mark.exception_test
def test_invalid_log_length(blockchain_test: BlockchainTestFiller, pre: Alloc, slice_bytes: bool):
"""Test deposit contract emitting logs with invalid log length (one byte more or less)."""
changed_log = DEFAULT_REQUEST_LOG[:-1] if slice_bytes else DEFAULT_REQUEST_LOG + b"\x00"

bytecode = Om.MSTORE(changed_log) + Op.LOG1(
0,
len(changed_log),
Spec.DEPOSIT_EVENT_SIGNATURE_HASH,
)
bytecode += Op.STOP

pre[Spec.DEPOSIT_CONTRACT_ADDRESS] = Account(
code=bytecode,
nonce=1,
balance=0,
)
sender = pre.fund_eoa()

tx = Transaction(
to=Spec.DEPOSIT_CONTRACT_ADDRESS,
sender=sender,
gas_limit=100_000,
error=TransactionException.INVALID_DEPOSIT_EVENT_LAYOUT,
)

blockchain_test(
pre=pre,
blocks=[
Block(txs=[tx], exception=TransactionException.INVALID_DEPOSIT_EVENT_LAYOUT),
],
post={},
)
Loading