Skip to content

Add configurable-sized contract templates to use with EELS - #114

Open
jochem-brouwer wants to merge 2 commits into
mainfrom
add-flexible-sized-contracts
Open

Add configurable-sized contract templates to use with EELS#114
jochem-brouwer wants to merge 2 commits into
mainfrom
add-flexible-sized-contracts

Conversation

@jochem-brouwer

Copy link
Copy Markdown
Member

Adds the templates which EELS introduced but which (size parameters) are missing here:

            case AccountMode.EXISTING_CONTRACT_MINIMAL:
                return MinimalContractInitcode()
            case AccountMode.EXISTING_CONTRACT_SAME_MAX:
                return StopJumpdestInitcode(
                    code_size=self.code_size, diff=False
                )
            case AccountMode.EXISTING_CONTRACT_DIFF_MAX:
                return StopJumpdestInitcode(
                    code_size=self.code_size, diff=True
                )
            case AccountMode.EXISTING_CONTRACT_JUMPDEST:
                return JochemnetPredeployContractInitcode(
                    code_size=self.code_size
                )

(ref: https://github.com/ethereum/execution-specs/blob/5fa5938b1ce01c661b3e9beaa403e4edddd11e1a/tests/benchmark/helper/account_creator.py#L303-L316)

In particular, the code size of the template can now be edited. Supports up to ~16MiB of contract size.

EELS might need: jochem-brouwer/execution-specs#4

@jochem-brouwer
jochem-brouwer force-pushed the add-flexible-sized-contracts branch from 780b55c to c76c554 Compare July 21, 2026 23:37

@CPerezz CPerezz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of the templates were added to the CI to be tested.

See docs/SKILL.md:

"add a covering entity to examples/full-matrix-spec-feature.yaml so CI exercises it."

Other stale docs after the additions:

  • internal/specbuild/full_matrix_test.go:32 — entity count pinned at 29
  • docs/SKILL.md — "29-entity" appears three times (frontmatter description, "Read these first", "Canonical spec reference")

Also, can we please remove drastically the verbosity of the comments? It's a lot of LOC just wasted in AI slop. Not really adding a ton of value.

Another note, for unique_jumpdest, the entry JUMP targets code_size - 1. JUMPDEST validity requires the target not be push-data, but the embedded address bytes at 0x2C..0x40 are decoded as opcodes during analysis, and an address byte that happens to be a PUSHn swallows up to 32 following bytes.
At code_size: 0x41 the target (0x40) sits immediately after the address, so for a large fraction of derived addresses the entry jump is invalid and any CALL exceptionally halts.

Comment on lines +31 to +68
// TestBuildUniqueJumpdestInitcodeMatchesEESTSizes pins keccak256 of the
// size-adjustable initcode against execution-specs
// `JochemnetPredeployContractInitcode(code_size=...)` for sizes on both
// sides of the PUSH2→PUSH3 entry-encoding boundary at 0x010000.
// Regenerate from an execution-specs checkout:
//
// import sys; sys.path.insert(0, 'tests/benchmark')
// from helper.account_creator import JochemnetPredeployContractInitcode
// from execution_testing import keccak256
// print(keccak256(bytes(JochemnetPredeployContractInitcode(code_size=CS))).hex())
//
// If EEST changes the Python initcode shape, update these constants in
// lockstep — both sides must always agree byte-for-byte.
func TestBuildUniqueJumpdestInitcodeMatchesEESTSizes(t *testing.T) {
cases := []struct {
codeSize uint64
keccak string
}{
{0x41, "0x38c595a4df0631d8df37721abc62f2a2834f09c6019cc4e0cab1af5b6dab2e6b"},
{0x100, "0xd925f01c3f4a9a5ce1d3bb1452d7ff2e020ed423b78a88261e6147eb0df586ce"},
// Default size: same pin as TestBuildUniqueJumpdestInitcodeMatchesEEST.
{0x6000, "0xb9cdb9047474294c9743cf3944156c844bf91763de66271493caa07a3de77ec5"},
// Largest PUSH2-encoded size (entry `PUSH2 0xFFFF; JUMP`).
{0x10000, "0xc255e15a2e24a89fd585858f4b20b46fe8eab3ce309d994be18f343f539823bb"},
// First PUSH3-encoded size (entry `PUSH3 0x010000; JUMP`).
{0x10001, "0xfb6c8fdbd5b4f1624cb84a9fc37181e6aab74f3bbb0990f58e6d13c49cd8baf8"},
{0x20000, "0xc03348b4fde3c0eed2f9707c8b003c62ae4752d85f36544d1a2ddad623114d07"},
// The PUSH3-capped maximum.
{0x1000000, "0x51f4a0dab520358d8b1a8c641f318e3fefbf722c164807c11c1ee76504ffc5d2"},
}
for _, c := range cases {
got := crypto.Keccak256Hash(BuildUniqueJumpdestInitcode(c.codeSize)).Hex()
if got != c.keccak {
t.Errorf("code_size=%#x: initcode keccak diverged from EEST: got %s want %s",
c.codeSize, got, c.keccak)
}
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstream EELS emits PUSH1 where this code emits PUSH2

So the pinned hashes for 0x41 and 0x100 in this test cannot be reproduced from an upstream checkout today, despite the test comment's "Regenerate from an execution-specs checkout" instructions.

Sizes in (0x100, 0x1000000] happen to agree because minimal and fixed widths coincide there; max_same/max_diff match upstream master at all in-range sizes already, since StopJumpdestInitcode has no jump target.)

Comment thread docs/SPEC.md
Comment on lines 128 to 132
Both `create2_deploys` and `create_preimage_deploys` accept an
optional `code_pattern:` parameter that opts into a named generator
producing per-derived-address byte-unique runtime. `code_pattern:` is
**mutually exclusive** with literal `runtime:` (and, on
`create2_deploys`, also with `initcode:`) — the pattern owns both.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Both create2_deploys and create_preimage_deploys accept an optional code_pattern:" — it's three templates now.

@jochem-brouwer

Copy link
Copy Markdown
Member Author

Another note, for unique_jumpdest, the entry JUMP targets code_size - 1. JUMPDEST validity requires the target not be push-data, but the embedded address bytes at 0x2C..0x40 are decoded as opcodes during analysis, and an address byte that happens to be a PUSHn swallows up to 32 following bytes.
At code_size: 0x41 the target (0x40) sits immediately after the address, so for a large fraction of derived addresses the entry jump is invalid and any CALL exceptionally halts.

I have to verify this, the address encoded here starts at 0x2C (44), and is 20 bytes, so it might seem like it runs to byte 64 (0x40), but this is not the case: bytes 44-63 are covered with the address, if we would also have byte 64 then this address would be 21 bytes long. So I don't think this is the case.

Regarding PUSH1/PUSH2, we should ensure the produced code matches whatever a live network produces, so will fix this. We could also just ban sizes below 256, I think the interesting use cases are the Osaka size, the Amsterdam size, the empty contract, and maybe small unique contracts (64 bytes).

Thanks for the review @CPerezz 😄 👍

Will address ASAP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants