Repository navigation
Conversation
bin/mega-evme, bin/mega-t8n, crates/mega-state-test and crates/state-test still target the legacy engine. Their sources stay untouched; they rejoin the workspace when they are ported to Satin.
Delete the 274 integration tests the test inventory marks legacy-only or retired (125 + 149) and move the 691 that survive into Satin under tests/_pending/, unchanged apart from the deletions. _pending has no main.rs, so Cargo does not build it; its README names the owning mechanism for every file. compute_gas/snapshot.txt goes with the per-opcode counter it pinned (D40).
revm 27.1 -> 40.0.3 with all twelve revm crates patched to the megaeth fork (rev 43485bf7), op-revm 8.1 -> 20.0.0 from the OP monorepo revision the node locks (f67d87cd) patched to the megaeth op-revm fork (rev 817e36e9), alloy-evm 0.15 -> 0.36, alloy-op-evm 0.15 -> 0.32, revm-inspectors 0.27 -> 0.40, alloy 1.x -> 2.x, alloy-primitives 1.6. The workspace becomes 2.0.0-alpha.1 (path-dependency versions in lockstep) and MSRV 1.94 (op-revm). The legacy core cannot build on revm 40, so its sources leave in the same commit: src/, the two examples and every bench target (the test gates rebuild them; the bench inputs under benches/data and benches/fixtures stay). The 181 inline unit tests the test inventory keeps move to tests/_pending/src/; the 107 it marks legacy-only or retired go with the code. The 11 mutants suppressions on limit/* go with limit/.
…tion table Satin is a single-spec engine: MegaSpecId has one rung, SATIN, running on Karst / Osaka. Display and FromStr use "Satin" and serde the variant name "SATIN", the same forms the legacy specs use. Legacy names (Equivalence .. Rex7) fail to parse with ParseMegaSpecError::Legacy and are never mapped to Satin. The discriminant continues after the legacy ladder (12). MegaHardfork has the single fork Satin. The activation table carries the mainnet and testnet chain ids with the Satin timestamp unset. The three spec tests the test inventory assigns to the Satin skeleton are ported here (the Isthmus/Prague mapping test now pins Karst/Osaka).
ExternalEnvFactory, ExternalEnvTypes, ExternalEnvs, SaltEnv, OracleEnv, EmptyExternalEnv, the bucket hasher and TestExternalEnvs come back from the legacy core as they were, with their seven unit tests. gas.rs (DynamicGasCost) stays out: it belongs to SALT pricing.
constants.rs holds the Satin placeholders in one table, each marked provisional: CPSB 1,530 (slot 97,920, account 183,600), CPHB 88, the 200,000,000 execution cap and the Rex6 data-size limits. types.rs names the transaction, halt reason, error and envelope types. The transaction is alloy-op-evm's OpTx, which carries the signed-tx conversions the node's block executor uses, so the core now depends on alloy-op-evm (its op-revm edge resolves to the op-revm fork).
MemoryDatabase, ErrorInjectingDatabase, BytecodeBuilder, GasInspector and right_pad_bytes come back from the legacy core; the only change is the DatabaseCommit signature, which takes EvmState on revm 40. transact() and the BytecodeBuilder execution tests follow with the engine.
MegaContext wraps op-revm's context shape (MegaTransaction, L1BlockInfo) and holds the MegaSpecId configuration next to the OpSpecId one op-revm executes on; both are written together. The spec fixes Karst on the Osaka gas table, EIP-8037 and EIP-2780 on, a 200M tx_gas_limit_cap, and EIP-7708 and the system-call reservoir margin off, whatever configuration the caller passes. Every Host and context method delegates to the wrapped context. MegaEvm wraps OpEvm over MegaContext with op-revm's precompiles and implements revm's execution, inspection and system-call traits and alloy-evm's Evm; MegaEvmFactory implements alloy-evm's EvmFactory and feeds each EVM the external environments of its block. Tests: an empty transaction, a value transfer and one SSTORE run through MegaEvm and through op-revm's OpEvm on the same CfgEnv and agree on every ResultGas field, the logs and the state; the empty transaction leaves exactly the gas above the 200M cap in the reservoir. Twelve of the unit tests the test inventory assigns to the Satin skeleton are ported; four that need later work are parked under the common execution layer, the Satin gas table, SALT pricing and the block executor with the reason in the pending README. test_utils::transact now takes the spec and the gas limit.
…nly modules The RLP decoding, signer recovery, deploy-address derivation and the IKeylessDeploy error ABI move from the retired sandbox module with their fourteen unit tests. Satin has one spec, so decode_keyless_tx drops its spec argument and always rejects trailing bytes (the Rex5 rule); the pre-Rex5 acceptance test goes. ParentBudgetExceeded goes too: it named the sandbox preflight, which the native CREATE sub-frame (native keyless deployment) retires.
…eleton limit/ and access/ are empty modules whose doc line names the mechanisms that fill them (the observe-stage-commit layer and the abort protocol, the data-size limit, detention, the state-gas limits and the KV count). MegaBlockExecutor only owns its EVM for now; block execution lands on it with the block executor.
Re-export the upstream crates consumers must build against (revm, op-revm, alloy-evm, alloy-op-evm, alloy-primitives, alloy-hardforks, alloy-consensus, alloy-sol-types, op-alloy-consensus), keep the short aliases (SpecId, Evm, EvmFactory, Context, BlockExecutor, ...), and warn on unused crate dependencies again. derive_more is only used by the test utilities, so it becomes an optional dependency of the test-utils feature.
Times ExecuteEvm::transact for an empty transaction and an ether transfer through MegaEvm and through op-revm's OpEvm on the same CfgEnv. It keeps one bench target alive until the test gates rebuild the suite.
The diff-scoped cargo-mutants run over this series left three survivors. Two were test gaps: InspectEvm::set_inspector and MegaContext::modify_chain now have tests that observe them. The third, From<MegaSpecId> for EthSpecId -> Default::default(), is equivalent while revm's default spec is Osaka, the spec SATIN maps to; it gets a line suppression, which stays live and turns into a no-op when a revm upgrade moves the default.
AGENTS.md now describes the satin branch: one spec (SATIN on Karst), the fork patches, the module table with the mechanism that fills each module, the _pending test layout, and the rule that today's main is the legacy engine and Satin is the only active spec. The unchanged conventions (test_ prefix, no_std, determinism, cargo sort, lint and format, one sentence per line, Prettier) stay as they were. The crate README follows, and REVIEW.md points at the parked mutation tests.
replay-bench builds state-test, which is no longer a workspace member: its bench job is disabled with if: false until state-test is ported to Satin (compare needs it and is skipped with it). The benchmark matrix shrinks to transact, the only bench target until the test gates rebuild the suite. The suppression-hygiene step grepped the spec-gate plan in a pipeline; on a single-spec engine the pack finds no gates, grep exits 1 and pipefail failed the step. The plan now runs on its own, so a planner failure still fails, and only the grep tolerates an empty result.
The data-only keyless module dropped the variant because its kind field was the legacy LimitKind. It comes back with the raw kind code of the IKeylessDeploy error, so every error the interface declares still encodes and decodes; the Satin meaning of the code is left to the limit mechanisms that define the resource dimensions.
test_utils gains zero_fee_l1_block_info() and op_transaction(), which the unit tests, the equivalence tests, the bench and transact() now share instead of four copies. The equivalence tests also check the gas table, EIP-7708 and the reservoir margin, and pin that the SSTORE draws no state gas on the Osaka table. The external-env getter test now observes the factory it was given, and the with_cfg test is named for what it checks.
AGENTS.md gets back two rules later mechanisms need (only CALL and STATICCALL reach interceptor dispatch; per-fork params are validated at load time) and says plainly that state gas is priced at zero until the Satin gas table lands; the crate README says the same and keeps one sentence per line. REVIEW.md's two pointers to moved or deleted files name both branches. The constants docs no longer cite an outside document. The pending README lists the 36 tests ported in place, which is why its counts sit below the test inventory's, and the manifest says why the bench inputs stay.
The spec-gate grep accepted any exit status; it now accepts only 1 (no match), so a real grep failure (exit 2) still fails the step.
Brings the review guide's update for #386. The guide's four conflicting passages keep this branch's facts (the forks pinned by rev, block execution admitting any inspector); the rest of the update merges as is and is refreshed for this branch's code when it merges into cz/feat/satin-w2.
…, on a per-frame byte pool (#407) Co-authored-by: mega-putin <mega-putin[bot]@users.noreply.github.com> Co-authored-by: William Cheung <git@troublor.xyz>
…om the journal once revm decided it The records, the state growth and the transfer log a start made on its frame are read off the journal against the start's accounts as they stood before revm decided it. The prediction stays, for now, for the two answers that keep nothing to read and as a debug check that the two agree.
An answer that failed kept nothing, revm having taken back what its start moved, so it is held to nothing and its result is revm's: a value call to a precompile that fails burns its forward as any failed precompile does, rather than being answered with a limit's stop. A precompile call gas detention answers from its price before revm runs it moved nothing, so it is held to the price alone: the stop is the compute stop, whatever the records its value transfer would have made.
What a start counts is read off the journal once revm decided it, so the prediction made before revm's decision, its debug check against the journal and the growth predictors go. The records a start's opcode prices stay, without the transfer log, which costs no history.
A start's records are counted from what revm did to its accounts. An answer that failed kept nothing and is held to nothing, its result the precompile's failure; a precompile answered from its price before its value moves is held to the price alone, the compute stop.
… state what a precompile's writes do to a start One rule reads what changed of an account since before revm decided a start, for a start's accounts and for a creator alike, and a start counts itself in one step. A node's own precompile that writes the start's accounts in its answer has those writes read with the start, which revm runs it inside; the docs say so, the spec's detention answer is a MUST, and stale prose about the start is restated.
…t a failed first answer is held to no limit
…ric and the review guide
|
High-level review (1/4): the legacy engine will run on the revm fork in the node, and does not build there yet This is about how #386 fits with the other repos, not about its own diff. I think it is a release prerequisite for Satin. The root Two problems:
Suggestion: treat "legacy ported to the fork API" and "Satin" as one release unit. The node, or legacy 1.8.x itself, sets |
|
High-level review (2/4): the revm fork carries MegaETH semantics, not only hooks The fork is described as "upstream v112 + thin hooks", and its own Rule 2 says it "adds hooks and data; it does not implement MegaETH semantics". At the pinned
The pieces that implement MegaETH semantics inside revm's own forwarding and settlement are:
One upstream public function also changes meaning. What this costs:
Suggestion: classify the fork commits in MEGAETH-FORK.md as the plan for the next base-line move:
For (c), it is worth checking whether an extension slot that is opaque to revm could replace the named semantics, with mega-evm forwarding and settling it through the hooks. |
|
High-level review (3/4): the larger frame result is a structural cost; measure it before Satin is sealed megaeth-labs/revm#76's own callgrind numbers, for a consumer that sets nothing:
Most of this comes from size, not from the hooks themselves. That baseline does not include mega-evm's own per-frame work: reading the journal before and after every frame start ( Because it comes from the data layout (the pools and the start charge sit on Suggestion:
|
|
High-level review (4/4): Satin's rules depend on EIP-8037 at a pinned commit and on revm's journal internals EIP-8037. Suggestion: write down an explicit "Satin's 8037 profile" with a freeze point. Later upstream changes to 8037 then become inputs to the next spec, instead of something the fork's gas core tracks. revm journal internals. The write-record definition (§10) depends on "the account's original info, which revm sets at the account's first load in the transaction". revm's journal behaviour is therefore part of consensus. The fork's tracking policy, however, takes "fixes that change execution results … verbatim", with no spec gate. Its first journal switch ( Suggestion: once Satin is active, any fork pick that changes an execution result is a hard fork. Add a rule to the fork's "Tracking upstream" section: after Satin is sealed, either such a pick lands behind a switch that a |
Summary
Satin is MegaETH's new execution engine: mega-evm 2.0, on the MegaETH forks of revm 40.0.3 and op-revm 20.0.0, running one unstable spec,
SATIN. It restates MegaETH's execution rules on Optimism Karst (Ethereum Osaka): EIP-8037 regular and state gas under a 200,000,000 execution cap, state gas scaled by the SALT bucket it lands in, a history-gas ledger for the bytes a transaction appends, data-size, KV-update and state-growth limits whose crossing reverts only the frame that crosses it, gas detention, native keyless deployment, the live system address read by the transaction itself, and the witness a stateless validator re-executes a block from. The legacy engine onmainkeeps executing every block before Satin activates.Do not merge this pull request until it is retargeted to
satin. #407 has merged into it, so the branch holds the whole engine. Its base,cz/feat/satin-review-base, exists only for the review (see "How to review").This pull request holds the whole engine. The stacked pull requests have merged into it, and their descriptions carry the detail:
satinbefore this pull request; against the review base, this pull request's diff includes it.mega-evmeon both engines and the byte-price grid (feat(satin): consensus limits as protocol params, mega-evme on both engines, byte-price grid #399).Each linked description covers its series as it was merged, and later series changed parts of it: #404 removed the KV and state-gas limits #387 added, fixed the limits per spec rather than as #399's chain parameters, and replaced #388's latched stop with a stop the caller can catch; the engine no longer deploys the system contracts #387 deployed, and only checks them in the block where Satin activates; #407 restored the KV-update and state-growth limits, counted on Satin's write records and state growth, removed #390's inspector settlement and admission gate in favour of revm's own inspected lifecycle, and replaced #388's instruction wrappers with the fork's hooks. Review against the final tree, the specification and the review guide, not against these descriptions.
How to review
.claude/skills/satin-review/SKILL.mdon this branch gives the scope, the design changes feat!: meter Satin's writes and frame starts in the revm fork's hooks, on a per-frame byte pool #407 made to what the stacked descriptions say, the module map with the invariants to check, the reading order, how to rate a finding, the revert-or-halt rule with its deliberate exceptions, and the decisions already made. It is removed before this pull request merges.git diff a8f8c7c9...cz/feat/satin-w2. Most of its deletions are the legacy engine the skeleton removed: skim them, or hide them with--diff-filter=d. Judge the final tree, not commit by commit.cz/feat/satin-review-base, which issatinbefore the skeleton (a8f8c7c9, the CI-only commit before it). The skeleton merged intosatinon its own, so againstsatinGitHub would show the engine without it; against the review base it shows all of it. The review base exists only for the review: this pull request is retargeted tosatinbefore it merges, and must never be merged into the review base.docs/spec/upgrades/satin.mdstates each Satin rule against Rex6; review the code against it.AGENTS.mdholds the implementation notes, andREVIEW.mdthe repository's review rubric.What is still to come
Nothing more in this pull request. #407 (
cz/feat/satin-w9) has merged into this branch with the rest of the engine: inspectors on revm's own inspected lifecycle, a rewritten result taken as given, one fixed gas budget for the pre-block calls, the cleanups, the KV-update and state-growth limits with their 98/100 frame shares, a creator's nonce record counted on the creator when its creation returns, and the same-transaction destruction table.This pull request is retargeted to
satinand merges there. After it, the fork pins move to the merged commits of the fork pull requests, and to tags once the line is final; follow-up pull requests onsatinsimplify the engine further, reading more of its accounting off revm's journal, and take up the performance work.Out of scope
main, which is frozen and executes every block before Satin activates. Satin never executes or decodes legacy-era data.fd230a3427d1b96c88e0b581bab8a7763c8ec298and the op-revm fork at2ecb7dceed1c458a2b59ca4d8993a073968cb111, byrev, the heads under review in feat: instruction and frame-loop hooks, and resource pools and a reservoir share on the gas tracker revm#76 (stacked on feat(gas): record the regular gas left at every out-of-gas failure; the reservoir report and the system-call margin move to mega-evm revm#75) and fix(handler): settle the first frame through the revm fork's shared settlement, pinned at megaeth-labs/revm#76 op-revm#8. How the engine uses a fork function is in scope.