Repository navigation
feat(test-specs): Test function receives fork's genesis pre-alloc - #3830
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## forks/bogota #3830 +/- ##
=============================================
Coverage 94.99% 94.99%
=============================================
Files 673 673
Lines 40888 40888
Branches 3769 3769
=============================================
+ Hits 38842 38843 +1
+ Misses 1429 1428 -1
Partials 617 617
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
55c6028 to
8572d8f
Compare
pre now starts with the fork's genesis accounts, so the skill says assigning replaces the whole account and del pre[addr] removes one.
An allocation built outside the pre fixture must now opt into the fork's genesis accounts with with_genesis_accounts(), which the spec merges in per fixture format; a genesis built without them raises instead of silently missing the system contracts.
fselmo
left a comment
There was a problem hiding this comment.
Hey @marioevz. Just some comments and I pushed a fix to your branch... feel free to drop it or redo it another way. A spec built with a pre that isn't the fixture (blockchain_test(pre={...}) or the fuzzer bridge path) got a genesis without the system contracts. It fails from Prague on, but at Cancun it filled silently with the wrong state root. Now an allocation built outside the fixture has to opt in (I made this fail loudly) with Alloc(...).with_genesis_accounts(), otherwise the spec raises. Fixtures are unchanged after this.
I also think that fund_address() on a predeploy wipes its code. This naming might be a bit confusing in case you think you're just funding the address but now it will also wipe other fields out. That's a quirk that I think from a devex perspective it's not ideal maybe but we could tidy this up in another PR if we think it's misleading. Thoughts on that?
Nothing blocking here though if you approve of my commit. Again, feel free to change it up and feel free to re-request a review if you do or I trust your judgement there too :)
Uses the update_account helper from ethereum#3830 so the predeploy keeps its nonce and storage, as Mario suggested on ethereum#3835.
* feat(tests): pin withdrawals before request system calls Replace each request predeploy's code with code that forwards its balance to a sink, so the post-state shows whether the withdrawal to it was credited before its system call ran. Runs from Prague. * refactor(tests): swap predeploy code with update_account Uses the update_account helper from #3830 so the predeploy keeps its nonce and storage, as Mario suggested on #3835.
Since ethereum#3830, `pre` holds the whole genesis of the fixture being filled, so the tests stop rebuilding it from the fork's pre-allocations. Fixtures are unchanged.
* test(tests): add MPT structural test suite
Fixtures whose committed state root is the cross-client oracle for the
Merkle Patricia Trie itself: storage slots and addresses are mined so
their keccak256 hashes force specific node operations, and every test
asserts the node path it claims against the reference `patricialize`
at fill time. Valid from Osaka on so every client consumes one set.
Covers leaf and extension splits, branch collapses onto a leaf, an
extension and a branch, extension merges, 16-way branches, deletes of
missing keys, the 31/32/33-byte inlining boundary for storage and
withdrawal leaves, and account-leaf deletion through a same-transaction
CREATE2 + SELFDESTRUCT onto a pre-funded mined address (EIP-6780).
* test(tests): add MPT leaf split sharing one nibble
pre: empty
post: ext(1) -> branch@1 -> {leaf(62), leaf(62)}
The shortest extension a leaf split can create, between the
four-nibble split and the prefix-free root branch.
* test(tests): add MPT leaf split below a root branch
pre: branch@0 -> {leaf(63), leaf(63)}
post: branch@0 -> {ext(1) -> branch@2, leaf} or {branch@1, leaf}
The split result is re-parented into an existing branch slot instead of
becoming the root, a different frame in every node-walking client.
* test(tests): add MPT ext split at its first nibble
pre: ext(4) -> branch@4 / ext(1) -> branch@1
post: branch@0 -> {ext(3) -> ..} / branch@0 -> {branch@1 -> ..}
A 4-nibble extension is shortened; a 1-nibble one vanishes without
leaving a zero-length extension behind.
* test(tests): add MPT collapse onto leaf under branch
pre: branch@0 -> {branch@1 -> {leaf, leaf}, leaf}
post: branch@0 -> {leaf(63), leaf(63)}
The survivor gains the branch nibble and stays in its parent's slot;
nothing merges because the parent is a branch.
* test(tests): add MPT collapse onto branch under branch
pre: branch@0 -> {branch@1 -> {leaf, branch@2}, leaf}
post: branch@0 -> {ext(1) -> branch@2, leaf}
A fresh 1-nibble extension appears between two branches instead of
merging into an existing one.
* test(tests): add MPT root branch collapse onto branch
pre: branch@0 -> {branch@1 -> {leaf, leaf}, leaf}
post: ext(1) -> branch@1 -> {leaf, leaf}
The root changes kind from branch to extension.
* test(tests): add MPT collapse onto ext under branch
pre: branch@0 -> {branch@1 -> {leaf, ext(2) -> branch@4}, leaf}
post: branch@0 -> {ext(3) -> branch@4, leaf}
The extension absorbs the branch nibble; nothing above it merges.
* test(tests): add MPT delete from a three-child branch
pre: ext(4) -> branch@4 -> {leaf, leaf, leaf}
post: ext(4) -> branch@4 -> {leaf, leaf}
The smallest non-collapsing delete; a client that reduces at two
remaining children passes 16 -> 15 and fails here.
* test(tests): add MPT delete-missing ending at a leaf
pre: leaf(64)
post: leaf(64)
Control: the walk for the absent key ends at a different leaf; the
root must equal the pre-state root.
* test(tests): add MPT mass delete leaving one child
pre: ext(4) -> branch@4 -> {16 x leaf}
post: leaf(64)
One block diff takes the branch from 16 children to a collapsed and
merged leaf; the existing cases cover 16 -> 15 and 16 -> 0.
* test(tests): add MPT one-to-sixteen from a leaf
pre: leaf(64)
post: ext(4) -> branch@4 -> {16 x leaf}
One split of a committed leaf followed by fourteen slot fills of the
branch it just created, all in one block.
* test(tests): add MPT insert then delete in one block
pre: leaf(64)
post: leaf(64), storage root byte-identical to genesis
Control: the transient write would have split the leaf; the block
diff must leave no residue of it.
* test(tests): add MPT delete then re-insert in one block
pre: ext(4) -> branch@4 -> {leaf, leaf}
post: the same shape, with the same or a new value
Control: a per-key delete marker must not shadow the later write.
* test(tests): add MPT collapse with an updated survivor
pre: ext(4) -> branch@4 -> {leaf = 1, leaf = 2}
post: leaf(64) = 7
The collapsed leaf must carry the survivor's new value, not its
committed one.
* test(tests): add MPT child replacement in a branch slot
pre: branch@0 -> {leaf a, leaf}
post: branch@0 -> {leaf b, leaf}
Child count unchanged while the slot's content is swapped; a
path-keyed cache must not serve the old leaf.
* test(tests): add MPT two shape changes in one block
pre: ext(2) -> branch@2 -> {l3, ext(2) -> branch@5}
post: ext(3) -> branch@3 -> {new, ext(1) -> branch@5}
A collapse at depth 2 and a split at depth 3 in one diff; neither the
pre shape nor either single-step result.
* test(tests): add MPT inline flip by path growth
pre: ext(4) -> branch@4 -> {c, ext(3) -> branch@8 -> {a, b}}, a and b
31 B inline
post: a = leaf(59) 33 B hashed under branch@4, then inline again
The flip comes from the path growing on collapse, not from the value.
* test(tests): add MPT account resurrection in one block
pre: .. -> branch@4 -> {survivor, doomed}
post: the same shape; doomed is a balance-only leaf again
A per-block destruction record must not shadow the later write.
* test(tests): add MPT storage-root flip with a collapse
pre: .. -> branch@4 -> {survivor contract, doomed}
post: .. -> leaf whose value carries the flipped storage root
Storage root and account re-pathing change in one block diff.
* test(tests): add MPT withdrawals root under 32 bytes
pre: no withdrawals
post: one 30-byte root leaf, withdrawalsRoot = keccak256 of its RLP
The root is hashed even below the inlining threshold; only the
withdrawals trie can reach the case.
* test(tests): add MPT withdrawals with empty-path leaves
pre: no withdrawals
post: branch@0 -> {8: leaf(1), 0: branch@1 -> {leaf(0) ..}}, 3 or 16
entries, inline and hashed leaves in one branch
Empty-path leaves (prefix 0x20) cannot occur in a state trie.
* test(tests): add MPT index tries by transaction count
pre: one sender, one recipient
post: the transaction and receipt tries at 17, 129, 130 and 258
transfers: nibble-1 slot, two-byte keys, a 1-nibble and a
3-nibble extension
The only extension nodes an index trie can contain.
* test(tests): add MPT constants for new cases
Slots: siblings of TWO_SLOTS_EXT4 at depths 2 and 3 (extension splits
under a branch parent), a pair sharing exactly 10 nibbles (32-byte leaf
from the value side), a triple where two keys share 9 nibbles and a
third shares 8 (inline-to-inline collapse), and sixteen slots with
distinct first nibbles (root-level 16-way branch). Addresses: siblings
of TWO_ADDRS_EXT4 at depths 1 and 3, salts sharing exactly 2 and 3
nibbles with it, and fifteen salts filling the SIXTEEN_ADDRS_BRANCH4
prefix, all for account-trie collapse cells with a controlled parent.
ROOT_PAIR is now spelled through SINGLE_SLOT, the identity five tests
rely on.
* refactor(tests): restyle MPT suite, pin cells
Every docstring now states the node path as `pre:`/`post:` lines in one
notation; client symbol citations are gone. Shape helpers move to
`trie_shape` (`storage_shape`, `account_shape`, `index_shape`,
`covering`, `leaf_rlp_size`). Delete tests are named `test_delete_*`,
genesis-only account tests `test_genesis_*`.
Account shapes are computed over the full genesis set (pre-alloc plus
the fork's system contracts, plus the coinbase after the block) and pin
the parent kind around the mined group with `covering`; the previous
`all(... if d == 4)` was vacuous when no node sat at depth 4. The
storage-root flip test now collapses onto the contract itself, created
through the factory with a salt whose address shares four nibbles with
the doomed one, so storage root and account re-pathing change in one
diff.
The three delete-missing tests merge into one parametrized control (the
write never reaches trie code), the trie-identical per-transaction
variant of the mass delete is dropped, insert-then-delete now covers a
leaf split, an extension split and a branch slot, and the index-trie
test adds counts 145, 257 and 273. Withdrawal values are encoded as a
plain RLP tuple instead of a fork-pinned class.
* test(tests): add MPT root branch collapse onto ext
pre: branch@0 -> {ext(3) -> branch@4 -> {p, q}, leaf(63)}
post: ext(4) -> branch@4 -> {p, q}
Closes the survivor-extension x parent-root cell; the root changes kind
from branch to extension. A client with a dedicated "extension becomes
root" path computes a different root here and nowhere else.
* test(tests): add MPT ext split under a branch parent
pre: branch@0 -> {ext(3) -> branch@4 -> {p, q}, leaf(63)}
post: the extension split at offset 0, in the middle, at its last
nibble, and the 1-nibble case where it vanishes
Every earlier extension split happened at the root; here the result is
re-parented into an existing branch slot, a different frame in every
node-walking client.
* test(tests): add MPT leaf collapse into a nested ext
pre: ext(2) -> branch@2 -> {l3, ext(2) -> branch@5 -> {l1, l2}}
post: ext(2) -> branch@2 -> {l3, leaf(61)}
The only extension-parent collapse whose merged leaf does not become
the root: the merge and the update of the parent branch's slot happen
in the same step.
* test(tests): add MPT delete-all from every root kind
pre: ext(4) -> branch@4 / branch@0 / leaf
post: empty
Adds root branch -> empty and root leaf -> empty next to the existing
root extension case, so every root kind reaches the empty trie root.
* test(tests): add MPT deletes around inline leaves
pre: ext(4) -> branch@4 -> {c, ext(3) -> branch@8 -> {a, b}} with a,
b inline (31 B) or hashed (33 B) by value size, c always hashed;
and ext(8) -> branch@8 -> {s, branch@9 -> {x, y}} all inline
post: deleted inline / hashed x survivor flips / stays hashed / stays
inline, each restored in a second block
Six cells of the (deleted child, survivor encoding) matrix; the
previous single flip test covered one. Hash-keyed stores decode inline
children from the parent and must re-inline a rebuilt survivor.
* test(tests): add MPT 32-byte leaf from the value side
pre: empty
post: ext(10) -> branch@10 -> {leaf(53), leaf(53)}, 30 B with a 1-byte
value and exactly 32 B with a 2-byte value
The existing 32-byte case comes from the key side (56 nibbles); a
client computing leaf lengths arithmetically has a separate branch for
a value byte >= 0x80, which only this cell reaches at the threshold.
* test(tests): add MPT inline leaf update, same size
pre: ext(8) -> branch@8 -> {leaf(55) 31 B, leaf(55) 31 B}
post: the leaf holds a different 1-byte value, still 31 B inline
Next to the existing flip to 33 B: the inline case rewrites the leaf
inside its parent's RLP and never gives it a hash, so dirty tracking
keyed by child hash has nothing to key on.
* test(tests): add MPT cells on client-built nodes
pre: empty; block 1 builds each geometry, block 2 deletes, block 3
re-inserts
post: the geometry's shape after the round trip
Adds three-to-two, leaf/ext/branch survivors under a branch parent,
both root collapses and the nested-extension leaf to the three-block
test. Genesis lands in a client's base layer; block-1 nodes live in
its diff layer or dirty cache, which is where these cells were still
untested.
* test(tests): add MPT two paths, two tries, shared node
pre: branch@0 -> {ext(3) -> branch@4 -> {p, q}, branch@1 -> {1, 14}};
two contracts with ext(4)/ext(5) tries; two contracts holding the
same single-leaf trie
post: both root children collapse, or one collapses while the other
splits; two storage roots change in one diff; one contract's copy
of the shared node is deleted, the other's grown next block
Touched-prefix sets with two entries under one root, parallel storage
roots, and hash-keyed stores that refcount or drop a node still used
by another trie.
* test(tests): add MPT nested split, root arity, dedup
pre: ext(2) -> branch@2 -> {l3, ext(2) -> branch@5}; branch@0 with 16
leaves; ext(4) -> branch@4 -> {p, q}
post: the inner extension replaced by a branch in one step; the root
branch filled, reduced to 15 and to 1; q round-trips to its
original value while p really changes
Extension-first-nibble splits so far happened at the root only; root
nodes take dedicated code in several clients; a touched-key applier
must neither skip the changed subtree nor rewrite the unchanged leaf.
* test(tests): add MPT account resurrection variants
pre: .. -> branch@4 -> {survivor, doomed}
post: the survivor credited in the collapse block; the doomed leaf
deleted, re-created by a transfer and deleted again across three
blocks; re-created by a withdrawal, or not when it carries 0 Gwei
The collapse must read the survivor's updated balance; a resurrected
balance-only account is deployable again; withdrawals apply after the
transactions and a zero credit leaves no empty account.
* test(tests): add MPT account collapse cells and arity
pre: mined siblings at depths 1 and 3 make the parent of the
collapsing branch a controlled branch or extension; three and
sixteen accounts under one branch
post: leaf, extension and branch survivors under a branch parent, a
branch survivor under an extension, 3 -> 2, and 16 -> 1 via
fifteen CREATE2 deletions in one block
Brings the account trie to parity with the storage cells; each cell's
parent kind is pinned over the full genesis set, so an uncontrolled
account sharing the prefix fails the fill instead of changing the cell.
* docs(tests): note empty genesis accounts inexpressible
`fund_address(addr, 0)` yields an empty account that the filler omits
from the genesis allocation, so EIP-161 clearing of a committed empty
leaf cannot be built through the public API. Recorded next to the other
inexpressible account cases.
* docs(tests): state the MPT coverage grids
The package docstring now carries the survivor x parent grid and the
enumerated classes (splits, root transitions, arity, encoding, one-block
sequences, index tries, account bookkeeping) so the "what is covered"
statement lives in the repository next to the tests.
* fix(tests): use the EIP-7997 genesis factory in MPT account tests
From Amsterdam the fork allocates the deterministic factory in genesis,
so the filler no longer injects it into the pre-alloc on first use of
`deterministic_deploy_contract`, and the `DETERMINISTIC_FACTORY_ADDRESS
in pre` check failed every account test that deletes through it.
Deploy through the factory only on forks without it, and check its
presence in the genesis instead of the pre-alloc.
* test(tests): pin MPT account shapes in every fixture genesis
A state test's own fixture starts from the pre-alloc plus
`pre_allocation()`, the blockchain fixtures derived from it from the
pre-alloc plus `pre_allocation_blockchain()`, but shapes were asserted
only in the second set. Two tests also funded the sender after taking
the snapshot, so its leaf was missing from every set they asserted.
Assert state-test shapes in both sets and take the snapshot once the
pre-alloc is complete. The fixtures are unchanged; 40 of the 92
account fixtures were previously not asserted on the genesis they
ship with.
* test(tests): add MPT collapses under a nested extension
pre: branch@0 -> {ext(1) -> branch@2 -> {d, ext(1) -> ..}, ..}
branch@0 -> {ext(2) -> branch@3 -> {d, branch@4}, ..}
post: branch@0 -> {ext(3) -> branch@4 -> {p, q}, leaf(63)}
Extension and branch survivors had an extension parent only at the
root, where the merged node becomes the new root; the nested case, in
which it is written into the parent branch's slot, existed only for a
leaf survivor. Both cells also run on client-built nodes. They reuse
the slots of the extension-split tests, so no new constants.
* docs(tests): state why the MPT account deletions need an ORIGIN beneficiary
The account tests delete a genesis-funded leaf by CREATE2-ing init code
that self-destructs to ORIGIN. From EIP-8246 a self-destructed account
keeps its balance and is only removed if that balance is zero, so the
deletion depends on the balance leaving the account. A self-destruct to
the contract itself would leave a balance-only account on Amsterdam and
later forks; state that next to the init code and in the module
docstring.
* fix(tests): assert the MPT shapes the docstrings claim
Four fill-time checks did not pin what their docstrings state:
- The inline-survivor test accepted 31 or 33 bytes for either deleted
leaf; each case now asserts its own size.
- The storage-root flip test claimed an extension above the collapsing
branch without asserting it.
- The withdrawal resurrection test asserted only the genesis shape; it
now asserts the two-child branch when the withdrawal re-creates the
account and the collapsed leaf when it credits nothing.
- The index test compared the root node of two paths of one trie,
which are always equal.
No fixture changes.
* refactor(tests): share MPT test helpers and table-drive the deletes
- `SLOT_WRITER`, `DELETE_INITCODE` and `GWEI` live in `constants.py`
next to the salts mined against them; both test modules import them
and encode writer calldata the same way.
- The twelve genesis-trie storage deletes repeated the fourteen cases
of the block-built delete test. One `DELETE_CASES` table now drives
both tests and pins each case's path before and after the delete,
where the block-built test only checked that the shape changed.
- The two standalone account collapses are rows of the parametrized
collapse test. Its survivor kind is the node below the collapsing
branch, and for extension and branch survivors it asserts that the
merged extension ends at the survivor's branch. Four blockchain tests
share one funding prologue.
- The sixteen-to-one storage delete is one transaction, so it is a
state test.
- Withdrawal leaves are sized from `Withdrawal.to_serializable_list()`;
a check of the reference trie's own root hashing is gone.
- `_alloc` gives way to the framework's `Storage`, `_shape` to
`account_shape`, and the root arity test takes its committed storage
as a parameter.
Fixture content is unchanged except for two cases: the account
extension-merge collapse funds its second address with 1 wei instead
of 2, and the sixteen-to-one storage delete gains state fixtures, its
blockchain fixtures now derived from them.
* docs(tests): tighten the MPT test docstrings
The package docstring states the method and defines the shape notation
once; the coverage inventory that duplicated the test list is gone.
Test docstrings give one sentence for the scenario and its rule, then
the `pre:`/`post:` paths, without per-case labels, `Control:` prefixes,
claims about client internals or comparisons with other software.
Helper docstrings use the imperative mood.
* fix(tests): fill the MPT account tests in separate pre-alloc groups
A shared group adds other tests' accounts to the genesis whose shape the tests assert, and the two storage-root flip cases collide on one address.
* refactor(tests): assert the MPT account shapes on the seeded pre
Since #3830, `pre` holds the whole genesis of the fixture being filled, so the tests stop rebuilding it from the fork's pre-allocations. Fixtures are unchanged.
* feat(tests): add MPT code-hash change on a collapse survivor
CREATE2 deploys code onto the balance-only survivor in the block that deletes its sibling, at the address `SURVIVOR_SALT` already places there.
Since #3830, a pre built outside the pre fixture must opt into the fork's genesis accounts, so the inclusion-list fixture tests call with_genesis_accounts().
Since #3830, a pre built outside the pre fixture must opt into the fork's genesis accounts, so the inclusion-list fixture tests call with_genesis_accounts().
Since #3830, a pre built outside the pre fixture must opt into the fork's genesis accounts, so the inclusion-list fixture tests call with_genesis_accounts().
One conflict, in the export list of specs/__init__.py: forks/bogota added ReceiptStatusExpected next to this branch's Reorg* exports. The resolution keeps both, in alphabetical order. Two forks/bogota API changes break ReorgTest without conflicting: - make_genesis takes the fixture format instead of apply_pre_allocation_blockchain (ethereum#3830). ReorgTest passes BlockchainEngineReorgFixture; the inherited genesis_pre_allocation gives the same blockchain pre-allocation as before. - validate_receipt_status is replaced by run_block_verifications. ReorgTest calls it under the same guard as BlockchainTest. A pre built outside the pre fixture must now opt into the genesis accounts, so the reorg unit tests call with_genesis_accounts().
* Add EIP-7805 on top of Amsterdam (#1928) * src(focil): add FOCIL src features + first set of FOCIL tests (#2643) * feat: continue focil implementation * feat: tests added * fix(tests): derive 7702 set-code gas in FOCIL test from fork calculator * feat: parametrize existing tests * chore: move focil tests to bogota folder * feat: update feature.yaml with focil release support * feat: Bogota fork + EIP-7805 engine_newPayloadV6 bump (#3028) * feat: Bogota fork + EIP-7805 engine_newPayloadV6 bump The first tests-focil release fixtures were filling FOCIL tests with `newPayloadVersion: 5` because no EIP class declared the version bump and there was no fork class to host EIP-7805. As a result `consume engine` appended `inclusionListTransactions` as a 5th positional arg to V5, which isn't a wire format defined by any Engine API spec — all clients reject it. This adds: - EIP7805 mixin under eips/bogota/ with engine_new_payload_version_bump and engine_forkchoice_updated_version_bump - Bogota fork class (Amsterdam + EIP7805), reusing Amsterdam's t8n - Switches FOCIL tests' valid_from marker to Bogota After refilling, fixtures now use engine_newPayloadV6 / forkchoiceUpdatedV5, matching the canonical Engine API PR (ethereum/execution-apis#626). * Address review: BogotaEIPs auto-discovery + feature.yaml --from=Bogota - Add BogotaEIPs sentinel that auto-discovers eip_*.py modules under eips/bogota/, mirroring AmsterdamEIPs. Bogota fork class now inherits BogotaEIPs instead of referencing eips.EIP7805 explicitly. - Switch focil entry in .github/configs/feature.yaml from --until=Amsterdam to --from=Bogota so the FOCIL job fills against the Bogota fork rather than ending at Amsterdam. --------- Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> * fix: focil release range * feat: support bogota in build matrix script * feat: print draft release url for easier discovery * Rename inclusion_list_satisfied field (#3092) * refactor(spec-specs): rename `inclusion_list_satisfied` part 2 (#3095) * fix(specs): Rebase issue * fix(specs): More rebase issues * fix(tests,tools): make the EIP-7805 FOCIL tests fill on the current Amsterdam (#3370) * fix(tests,tools): make the EIP-7805 FOCIL tests fill on the current Amsterdam 21 of the 24 FOCIL tests do not fill on this branch. Three independent causes, none of them in the tests' assertions. t8n passed decoded transactions where raw ones are expected. check_inclusion_list_transactions takes Tuple[LegacyTransaction | Bytes, ...] and calls get_transaction_hash, which asserts isinstance(tx, (LegacyTransaction, Bytes)); the caller passed convert_transaction(...), which returns a decoded fork transaction, so every typed-transaction case died on AssertionError. Both tuples now go through encode_transaction. The scenarios left no gas budget for the block's own access list. They size block_gas_limit to their transactions alone, but the EIP-7928 budget is block_gas_limit // GAS_BLOCK_ACCESS_LIST_ITEM, and the system-contract predeploys touched every block plus senders, recipients and coinbase come to about 30 items against a limit of about 22. Raising the limit alone would change what the scenarios assert, because the gas a pending inclusion-list transaction is allowed comes from block_gas_limit minus the gas the included transactions use. So build_block raises the limit and spends exactly the same amount on ballast transactions, leaving remaining_gas -- and therefore every "fits" and "does not fit" boundary -- unchanged. The ballast is a whole number of empty transfers so the amount is always representable on the fork's calldata-cost lattice. One funding transfer predated value-carrying transactions costing extra intrinsic gas. test_unsatisfied_when_block_tx_funds_pending_il_sender sizes and funds alice's transfer with calc(), but it carries value, so it needs calc(sends_value=True) and was failing INTRINSIC_GAS_BELOW_FLOOR_GAS_COST. Assertions are untouched: of the changed lines in test_focil.py, none touch expected_status, pytest.param, assertions, sender balances, nonces or inclusion-list composition. Scenario ids are unchanged and the reference implementation validates all 24 expected outcomes during fill. * Apply suggestions from code review Co-authored-by: Mario Vega <marioevz@gmail.com> * fix: lint --------- Co-authored-by: Mario Vega <marioevz@gmail.com> * fix(specs): fix check_inclusion_list_transactions * fix(tests): fix import * refactor(test-specs,tests): Refactor inclusion-list spec definitions, existing tests (#3373) * feat(test-specs): Add inclusion list satisfied verification to block * fix(test-specs): fix framework on missing inclusion list * refactor(tests): refactor existing focil tests * nit(test-specs): Unnecessary comment * Apply suggestions from code review Co-authored-by: spencer <spencer.tb@ethereum.org> Co-authored-by: Mario Vega <marioevz@gmail.com> --------- Co-authored-by: spencer <spencer.tb@ethereum.org> * feat(specs,test-specs,tests): Update Engine API to latest Bogota spec, implement IL inclusion_test (#3401) * fix(specs,test-forks): Fix Bogota filling with EIP-7805 * fix(test-fixtures,test-specs): Update Engine API to latest version * fix(test-fixtures): Label suffix fix (backport safe) * fix(test-fixtures): Unit tests (backport safe) * feat(test-specs): Implement inclusion list variant of inclusion_test marked tests * bug(specs): Fix type-3, invalid RLP, and invalid chain id txs in inclusion lists * bug(test-specs): Fix inclusion_test filling * fix(tests): Blob test * fix(tests): clear rlp_modifier when building inclusion-list variants (#3406) The inclusion-list variant moves the last transaction of the last block out of the block body and into the inclusion list, then clears the expectations derived from it: header_verify, expected_gas_used and expected_block_access_list. rlp_modifier was left in place. rlp_modifier force-writes header fields onto the built block after the transition tool has run, and tests compute it from the pre-move transaction list. The moved transaction therefore keeps contributing to the header even though it no longer executes in the block, producing fixtures whose header contradicts their own body. Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> * fix(tests): count top-frame state gas once in FOCIL OOG scenarios * chore(ci,test-consume,test-specs,tests): backport devnets/focil/0 fixes to eip-7805 (#3595) * fix(github): EIP-7805 Devnet config Backported without the whitespace-only runs-on reformatting. (cherry picked from commit 5f46130) * fix(consume): require null inclusionListSatisfied on non-VALID payloads (#3470) Port the engine simulator's inclusionListSatisfied guard from #3410 to the devnets/focil/0 simulator. The check previously asserted a non-null response field whenever the fixture stamped one, regardless of the payload's expected status; execution-apis bogota.md requires the field to be null for any payload not deemed VALID, so the check demanded a spec violation. tests-focil-devnet v0.2.0 stamps the field on 5,489 expected-INVALID engine fixtures, which fail for every client on the Hive focil board with "expected inclusion_list_satisfied in response". The ported guard is verbatim from #3410: a payload not deemed VALID must report null, and only VALID payloads have the fixture's expected verdict enforced. Fixes #3436 for the consume path independently of a fixture refill. (cherry picked from commit 67a9345) * fix(tests,test-specs): fix invalid inclusion-list engine fixture generation (#3471) * fix(tests): clear rlp_modifier when building inclusion-list variants (#3406) The inclusion-list variant moves the last transaction of the last block out of the block body and into the inclusion list, then clears the expectations derived from it: header_verify, expected_gas_used and expected_block_access_list. rlp_modifier was left in place. rlp_modifier force-writes header fields onto the built block after the transition tool has run, and tests compute it from the pre-move transaction list. The moved transaction therefore keeps contributing to the header even though it no longer executes in the block, producing fixtures whose header contradicts their own body. Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> * fix(focil): omit inclusion list result for invalid payloads * fix(tests): give test_tx_gas_limit a block gas limit a valid block fits in The test pinned the block gas limit to 21000 with a 21001-gas transaction to trigger GAS_ALLOWANCE_EXCEEDED. On forks with the EIP-7928 block-access-list item budget (gas_limit // 2000) a 21000-gas-limit block allows 10 items, which is below the protocol-level writes of even an empty block, so no block in this environment can be valid. The test itself never noticed - its block is expected invalid - but its auto-generated inclusion-list variant moves the failing transaction into the inclusion list and expects the emptied block to be VALID, producing a fixture that the spec's own state transition rejects (tests-focil-devnet@v0.2.0, blockchain_test_engine_inclusion_list, the one non-blob entry in the 127 broken engine fixtures). Scale the numbers to 100_000/100_001: the allowance check still fires the same way, and the item budget (50) now accommodates an empty block, so the inclusion-list variant fills to a genuinely valid block. * fix(test-specs): splat the metadata regression block from untyped kwargs The regression test from #3445 constructs a BuiltBlock through model_construct with object() sentinels for fields that get_fixture_engine_new_payload only forwards, which the typechecker rejects for missing and mistyped named arguments. model_construct skips validation and the sentinels are never read, so splat them from a dict[str, Any] instead of passing them as checked named arguments. * fix(tests): align test_tx_gas_limit with its #3566 counterpart Byte-identical to the forks/amsterdam hunk so the next focil rebase resolves cleanly; fixture output is unchanged. --------- Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: chugarchugarr <josephlerma19@gmail.com> Co-authored-by: Felipe Selmo <fselmo2@gmail.com> (cherry picked from commit 65f4222) --------- Co-authored-by: marioevz <marioevz@gmail.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: chugarchugarr <josephlerma19@gmail.com> * fix(ci,test): fix issues with PR CI runs in focil branch (#3594) * fix(tests): use a nonce collision in the FOCIL creation-collision scenario * refactor(spec-specs,test-forks,tests): move EIP-7805 onto the Bogota fork module * fix(test-forks): pin Bogota's FOCIL engine method versions The EIP-7805 mixin bumps Bogota to engine_newPayloadV6 and engine_forkchoiceUpdatedV5; the fork test still pinned the pre-FOCIL newPayload version. * docs(spec-specs): drop blob exclusion claim from the IL check The inclusion list check never skipped blob transactions, and EIP-7805 does not ask it to; test_block_with_pending_blob_il_tx_is_valid expects an appendable blob transaction to leave the list unsatisfied. * refactor(spec-specs): drop repeated sender checks in the IL check check_transaction already recovers the sender and runs validate_transaction, so the IL check now mirrors process_transaction: the chain id check, then check_transaction. * docs(spec-specs): keep the forks/bogota fork docstring EIP branches that each rewrite the Bogota fork docstring conflict when the devnet workflow merges them, so this branch leaves it as forks/bogota has it; the EIP is listed there when it lands. * fix(tests): skip inclusion-list variants whose entry cannot fit a list (#3502) * fix(tests): skip inclusion-list variants whose entry cannot fit a list * fix(test-specs,test-forks): fail oversized IL entries a test wrote Only the inclusion-list variant moves a test's own transaction into the list, so only it skips an entry over the limit; a list the test wrote fails with a test correctness error. The limit is now a fork accessor named after MAX_TRANSACTIONS_BYTES_PER_INCLUSION_LIST, and the unit test pins both sides of it for both cases. --------- Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: fselmo <fselmo2@gmail.com> * feat(consume): check inclusionListSatisfied semantics and probe engine_getInclusionListV1 (#3410) * feat(consume): check inclusionListSatisfied on forkchoice responses to a valid head * feat(consume): probe engine_getInclusionListV1 after chain import on inclusion-list fixtures * feat(test-forks): version engine_getInclusionList by fork EIP-7805 introduces engine_getInclusionListV1, so its mixin now bumps the method version like the other Engine API methods, and the method version test pins it at Amsterdam and Bogota. * fix(consume): check only what getInclusionList must return The spec leaves transaction selection to the client and caps the sum of the transactions' bytes, so the probe no longer requires the seeded transactions back and no longer counts the list's RLP header. Its method version and size limit come from the head block's fork. * fix(test-specs): omit inclusionListSatisfied on engine API errors A payload rejected with an engine API error code is not deemed VALID, so its fixture carries no inclusionListSatisfied, as for payloads with an expected exception. * fix(test-specs): limit inclusion-list variants to engine formats Only the engine and engine X simulators check inclusionListSatisfied, so sync and stateful fixtures no longer get an inclusion-list variant. No inclusion test generates one today, so no fixture changes. * fix(test-specs): reject inclusion tests the variant cannot use An inclusion test whose last block has no transaction, or is invalid only through block exceptions, now fails as a test correctness error instead of crashing or filling a wrong satisfied expectation. * fix(spec-specs): raise catchable errors for bad transaction bytes decode_transaction indexed empty bytes and asserted on a 0xff first byte, so the inclusion list check crashed on such entries instead of skipping them. Both now raise an InvalidTransaction. --------- Co-authored-by: fselmo <fselmo2@gmail.com> * feat(tests): Increase coverage for EIP-7805 tests (#3804) * feat(test-specs): send inclusion list entries as raw bytes Entries reach the transition tool and the fixture as the same bytes, so the verdict is computed on what clients receive, including rlp_override. Block.inclusion_list_txs also accepts Bytes entries for malformed cases. * feat(test-forks): add the inclusion list blob-entry predicate The inclusion list variant now reads fork.inclusion_list_counts_blob_entries() for a moved blob transaction, so a spec change on blob entries is a one-line flip in the EIP-7805 mixin. * feat(tests): skip undecodable inclusion list entries Each malformed entry (empty, a 0x00 byte before a legacy transaction, type bytes 0x7f, 0xff and the first undefined type, a known type with another type's payload, a trailing byte, truncated legacy) leaves the payload satisfied alone and lets the check reach an appendable transaction after it; two well-formed controls show the reused payloads are appendable. * feat(tests): extend FOCIL appendability coverage Covers every non-blob type through with_all_typed_transactions, the fee cap and value in the balance check, withdrawals funding a sender only from the next block, priority fees funding the fee recipient, a delegated sender drained without a nonce change, an entry past one member's byte limit, and entries that count as included only by their exact bytes (access list or authorization list changed); the blob test reads the fork's blob-entry predicate. * feat(tests): mark invalid-authorization set-code txs as inclusion tests A set-code transaction whose authorizations all fail is still valid, so its inclusion list variant expects an omitted copy to leave the payload unsatisfied. * feat(tests): cover more FOCIL appendability checks Add L2 type bytes 0x7d and 0x7e to the undecodable-entry cases, gas limits above the transaction cap at each block room's edge, and one-field pairs for chain id, base fee and sender code. * feat(spec-specs,test-forks): skip blob entries in the IL check EIP-7805's author confirmed that a blob transaction in the inclusion list is ignored for satisfaction, so the check skips it and the fork's blob-entry predicate flips to false. * feat(test-forks): add the merged inclusion list size limit The consensus layer merges up to 16 committee members' lists into the one sent with a payload, so it carries at most 128 KiB; a test that writes a larger list fails to fill. * feat(tests): pin FOCIL ordering, encoding and merged list size Adds a system call funding a pending sender before and after the block's transactions, non-canonical RLP entries, and a full 128 KiB merged list whose last entry still counts. * fix(tests): stop the FOCIL transfer size search from looping Re-signing can shorten r or s by a byte, which left the length search stuck one byte either side of the target for about 1 in 80 sender keys. A miss now also changes the calldata byte, which draws a new signature; all 400 probed keys converge within four tries. * refactor(test-specs): send malformed IL entries through rlp_override Block.inclusion_list_txs takes Transactions again; a malformed entry is a transaction with rlp_override, the idiom malformed block transactions already use. The framework still passes each entry's rlp() bytes to the transition tool and the fixture, so the verdict is judged on exactly what clients receive. * refactor(test-forks): drop the inclusion list blob-entry predicate EIP-7805's check now skips blob transactions outright (ethereum/EIPs#12470), so the fork method that hedged it was False on every fork. The inclusion list variant expects a satisfied payload when the moved transaction is a blob transaction, and its unit test no longer monkeypatches the predicate. * refactor(test-forks): derive the merged IL size from the list limit The merged inclusion list limit is now the committee size times the per-list limit, so 8 KiB has one source. * feat(tests): pin FOCIL blob and chain id skips Blob and wrong-chain entries now also run before an appendable transaction, so a client that stops at a skipped entry fails. The blob test adds a control that includes the blob transaction in the block, and the chain id test adds type-2 and unprotected legacy entries. * feat(tests): spend state gas before the FOCIL gas-cap state edge The block now also creates an account, so the pending transaction's state room is the block gas limit minus state gas already used. A client that ignores block state gas used fails the one-over case. * refactor(tests): name FOCIL type-2 fields and trim docstrings type_2_entry keys its raw field overrides by RLP field name and reads chain id and to from the transaction instead of decoding its own encoding. Also trims the undecodable-entry docstring and sets the match-by-bytes expectation in each branch. * refactor(tests): set FOCIL expectations in each branch The new FOCIL tests now name the expected verdict in each branch and use match like the rest of the module. The base fee pair is named for its boundary, and the same_bytes case says it is the control. Fixtures are unchanged. * docs(spec-specs): name every skip in the IL check docstring The inclusion list check docstring named only the blob skip; it now also names undecodable entries and entries signed for another chain. * refactor(test-specs): fund the IL unit test's signing sender The IL verdict unit test funds the sender the default key signs for, instead of a hard-coded address that would leave the entry unfunded if that default changed. * feat(tests): tighten FOCIL entry matching and drain cases The bytes-matching test gains an entry that changes only the signature's s, and the drained-sender test takes one wei instead of the whole balance, so the pending tx misses being affordable by one wei. The sized transfer reads its nonce from the sender. * feat(tests): skip FOCIL legacy entries with over-64-bit chain ids A pending legacy IL tx whose v encodes this chain's id plus 2**64 has an invalid v and is skipped, so a client that truncates the chain id to 64 bits, and counts the entry, fails it. * chore(ci): fill devnet releases for every fork through Bogota Devnet releases from this branch now fill --until=Bogota, split across the fork ranges, as the frames and bogota devnet releases do (#3418), instead of Bogota alone. * fix(test-specs): seed genesis accounts in FOCIL fixture tests Since #3830, a pre built outside the pre fixture must opt into the fork's genesis accounts, so the inclusion-list fixture tests call with_genesis_accounts(). * ✨ feat(tests): Extend focil test coverage (#3868) * ✨ feat(tests): Extend focil test coverage * fix(tests): drop duplicated parametrize line * fix(tests): keep the parity bit in the y_parity_2 IL case With v=2, a client that reads only the low bit recovers an unfunded sender for types 2 and 4, so the list was satisfied for the wrong reason; v + 2 recovers the funded sender instead. * test(tests): drop the IL authorization-list test The inclusion_test variant of EIP-7702's test_empty_authorization_list already covers the empty list, and the type-4 base-fee case covers an appendable one. * refactor(tests): derive the IL signature test's gas limit The gas limit comes from the fork's intrinsic cost calculator, and each test builds the one authorization a type-4 tx needs. * refactor(tests): run IL signature and base-fee cases on all typed txs with_all_typed_transactions picks up every non-blob type a fork adds, where the hand-written type list would not. * fix(tests): fund the key a lenient s_zero recovery yields With s = 0, a client without the s > 0 check recovers the key -z / r and found that sender unfunded, so the case passed for the wrong reason. --------- Co-authored-by: raxhvl <raxhvl@users.noreply.github.com> Co-authored-by: fselmo <fselmo2@gmail.com> * feat(spec-specs,test-forks,tests): implement EIP-8198 quick slots (#3703) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> * fix(tests): price the value transfer in blob transition txs The pre-fork blob transactions send value to an existing EOA, which EIP-2780 charges in the intrinsic gas, so every one was rejected once a transition from Amsterdam changed the blob schedule. * docs(spec-specs): keep the forks/bogota fork docstring EIP branches that each rewrite the Bogota fork docstring conflict when the devnet workflow merges them, so this branch leaves it as forks/bogota has it; the EIP is listed there when it lands. * feat(tests): extend EIP-8198 coverage (#3812) * feat(tests): reject the new blob fee fraction one block early A client that switches to EIP-8198's update fraction on the last Amsterdam block prices blobs lower and accepts every existing fork test; the new case offers that lower price there and expects the transaction rejected. * docs(tests): correct the EIP-8198 blob suites coverage entry The outside blob suites run at the Amsterdam to Bogota transition through two tests only, and this branch changes one of their files, so the entry names them instead of saying they cover the transition unchanged. * refactor(tests): derive the EIP-8198 base fee rounding edges The base fees where an empty block stops lowering the fee and a full block's rise grows past one wei now come from the fork's numerator, denominator and elasticity, so a new ratio moves them; the values at 5/48 are unchanged. * refactor(tests): parametrize the blob fee rejection by block index The two cases now differ only in which block is rejected, as in test_base_fee_wrong_rule_across_fork, so the unreachable raise goes; the filled fixtures are unchanged. * docs(tests): name the base fee case instead of its value The comment now points at first_larger_rise_after_full_block, since the base fees are derived from the fork. * fix(spec-specs,tests): keep the blob schedule unchanged at Bogota (#3871) * fix(spec-specs,tests): keep the blob schedule unchanged at Bogota EIP-8198 pairs a slot duration change with an EIP-7892 blob schedule entry at the upgrade, so Bogota inherits the blob target, maximum and update fraction like any named fork after Prague. Drop the provisional 12/18 values from the fork and the tests that asserted them. * feat(tests): pin the blob schedule unchanged at the Bogota fork Rewrite the removed blob transition tests to check that the fork block keeps the parent fork's blob target, maximum, and update fraction, and mark the block-level constraint section not applicable. * refactor(test-forks,tests): drop the blob schedule entry mentions Remove the sentences describing the EIP-7892 entry, which this EIP no longer carries, and take the fork block's blob counts from the new fork as the other transition tests do. --------- Co-authored-by: fselmo <fselmo2@gmail.com> * feat(spec-specs,tests): derive Bogota base fee from slot spacing (EIPs#12552) (#3873) * feat(spec-specs,tests): derive the bogota base fee change from slot spacing * fix(spec-specs,test-forks): cap the Bogota base fee change at 1/8 Count a spacing longer than twelve seconds per slot as twelve, as EIPs#12552 now specifies, so the change never exceeds the Amsterdam rule's. The framework reads the denominator from the parent fork instead of the 8 * 12 literal. * feat(test-exceptions): add INVALID_SLOT_NUMBER EIP-8198 rejects a block whose slot number does not exceed its parent's, so tests need an exception to expect for it. * feat(tests): pin the EIP-8198 fork block, cap and slot rule Check that the fork block keeps the 1/8 rule, including after a missed first slot, that spacing above twelve seconds per slot is capped, that a slot gap whose product exceeds 64 bits is handled, and that the slot number must increase from the fork block on. * fix(tests): restore the osaka BPO transition parent timestamp With the blob schedule unchanged at Bogota, this test no longer reaches a Bogota transition, so the earlier timestamp change is not needed. * feat(test-forks,test-specs): add header_slot_number_must_increase Name the EIP-8198 slot ordering rule as its own fork accessor, and give a state test's block one slot number shared by its state and blockchain fixtures, defaulting to one slot after the genesis block. * refactor(tests): gate the SLOTNUM slot cases on slot ordering Branch on header_slot_number_must_increase instead of the base fee predicate, since slot ordering is the rule the cases work around. * feat(tests): cover a fork block after two missed slots Add a fork block whose spacing per slot is not a whole number of seconds, derive the cap cases from the reference slot duration, and space the missed-slot case at the usual slot duration. * docs(spec-specs): note the slot number rule in validate_header * feat(tests): cover uneven slot spacing and the 64-bit cap Add base fee cases whose spacing per slot is not a whole number of seconds, and a pair either side of where the twelve-second cap exceeds 64 bits. --------- Co-authored-by: fselmo <fselmo2@gmail.com> * fix(tooling): build the docker images on uv's trixie base (#3882) Astral stopped publishing bookworm uv images after 0.9.30, so the python3.12-bookworm-slim base is stuck below the uv 0.13.4 that #3880 requires and the repository image fails to build. Use the python3.12-trixie-slim tag, which follows current uv. * Besu: add BlockException.INVALID_SLOT_NUMBER mapping Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.com> --------- Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.com> Co-authored-by: Jihoon Song <jihoonsong@users.noreply.github.com> Co-authored-by: felix <felix314159@users.noreply.github.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: marioevz <marioevz@gmail.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: spencer <spencer.tb@ethereum.org> Co-authored-by: fselmo <fselmo2@gmail.com> Co-authored-by: chugarchugarr <josephlerma19@gmail.com> Co-authored-by: spencer <spencer.taylor-brown@ethereum.org> Co-authored-by: raxhvl <10168946+raxhvl@users.noreply.github.com> Co-authored-by: raxhvl <raxhvl@users.noreply.github.com> Co-authored-by: Barnabé Monnot <b@barnabemonnot.com> Co-authored-by: Barnabas Busa <busa.barnabas@gmail.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Since #3830, a pre built outside the pre fixture must opt into the fork's genesis accounts, so the inclusion-list fixture tests call with_genesis_accounts().
Description
The
prefixture now starts with the accounts the fork allocates at genesis, and the spec usespreas the genesis allocation unchanged. Previouslyprestarted empty and each spec merged the fork's pre-allocation underneath itwhile filling, so a test could not see, and could only partly control, the genesis it ran on.
Example of how this is useful: #3600 requires being able to inspect the final
preit runs on because its tests depend on a specific state trie shape. With the spec filler modifying the pre-allocation after the test function finished, the test required calling specific methods in order to try to predict the modifications in advance, which was brittle but no other way was possible.Behaviour changes for test writers
The test now owns
preand has the final call: it may inspect, replace or delete the seeded accounts, and the spec does not correct or re-add them.pre[addr] = Account(...)replaces the whole account. Before, it was merged field by field over the fork's default, so unset fields kept the predeploy's values.del pre[addr].pre.deploy_contract(address=...)at a predeploy's address now fails withalready in allocation(because the contract is already there). Usepre[addr] = Account(...)to replace the predeploy.The tests that relied on the old merge are updated to say what they mean:
del pre[addr]for removals,pre[addr] = Account(...)for replacements, and.copy(...)where only the code changes. The same applies to the system contract deployment generator intools/utility/generators.py. Framework unit tests that build specs by hand seedpreexplicitly.Verification
All fixtures are unchanged:
fill tests --from Osaka --until Bogota --generate-all-formats, before and after, compared fixture by fixture across 258,660 fixtures and every pre-alloc group. The only differences came from the three modules that partially override predeploys. After moving them to.copy(...), those modules were filled again through Bogota with all formats, and they are identical too.test_contract_deployment.pymodules, which fill transitions into Prague, are filled separately through Bogota with all formats: identical.just test-testspasses.Related Issues or PRs
Checklist
just static<type>(<area>): <description>, where<type>and<area>come from an appropriateC-<type>, respectivelyA-<area>, label. The title should match the target squash commit message.Cute Animal Picture