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.
… the crate README
…te results under the latch
…ites in debug builds
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This is the first commit series of the Satin wave-1 pull request, which lands the Satin skeleton, the test gates and the common execution layer in that order and then takes the differential harness out of the repository: the workspace moves to the MegaETH forks of revm 40.0.3 and op-revm 20.0.0, the legacy core leaves, and
crates/mega-evmrestarts from an empty skeleton that runs a single spec,SATIN, through op-revm'sOpEvm, which the common execution layer replaces with its own frame lifecycle over op-revm's handler.cargo tree -i revmshows one revm, from the fork; revm 27 is gone from the lockfile. The workspace becomes2.0.0-alpha.1(path-dependency versions in lockstep), MSRV 1.94.1 (the minimum of the locked alloy 2.4.2 crates;cargo metadata --lockedreports no dependency above it, and the workspace checks on the 1.94.1 toolchain).bin/mega-evme,bin/mega-t8n,crates/mega-state-test,crates/state-test) leavemembers/default-members; their sources are untouched and they rejoin when they are ported to Satin.MegaSpecId::SATIN(Karst / Osaka) andMegaHardfork::Satin; legacy spec names are rejected, never mapped.MegaContextwraps op-revm's context and fixes the Satin switches: EIP-8037 and EIP-2780 on,tx_gas_limit_cap= 200,000,000, EIP-7708 and the system-call reservoir margin off, Osaka gas table.MegaEvmwrapsOpEvm(the common execution layer below replaces that with revm'sEvmand its own handler) and implements revm's execution traits and alloy-evm'sEvm;MegaEvmFactoryimplements alloy-evm'sEvmFactory.external/is carried over unchanged;limit/(filled by the common execution layer) andaccess/are empty placeholders;system/keylessholds the keyless data helpers;constants.rsholds the provisional numbers;block/has the chain activation table (Satin unscheduled) and an executor skeleton.crates/mega-evm/tests/_pending/with a README naming the owner of each file, 36 unit tests ported in place. New: an empty transaction, a value transfer and one SSTORE run throughMegaEvmand through op-revm'sOpEvmon the sameCfgEnvand agree on everyResultGasfield, the logs and the state; spec round-trip and legacy-name rejection tests.MegaEvmand stock revm 43 with no unexplained difference (the fourth series takes it out of the repository; the scenario format stays); thecorpusandfactorybenches jointransactunder CodSpeed with a written policy; an execution-spec workflow runs the fork's own runner at the pinned tag with pinned counts; the mutation rules gain a reference point, a sharding rule, a shard mode and a second scope over the gates' own infrastructure, and the parked legacy mutant killers are retired.MegaEvmrunsMegaHandlerover op-revm's handler with its own frame lifecycle; the Host stages whatSSTORE,LOGandSELFDESTRUCTobserve and wrappers commit it after the opcode; data-size bytes and write records are counted per frame; a limit stops a transaction with a latched revert (MegaLimitExceeded), never a halt; synthetic frame results carry the inherited reservoir; block execution admits only declared read-only inspectors;execute_transactionreports the regular, state and history ledgers. No limit is enforced by default, and with none configuredMegaEvmstill equals op-revm; the layer was certified against stock revm 43 (0 unexplained differences over 352 scenarios) before the harness left the repository. All 41 parked rows of this layer and 4 depth-guard rows are ported, and the six owed static-callee halt reasons are asserted again.crates/mega-differential, itsdifferentialcheck and the 35 lockfile packages only it used are gone, and nothing else in the lockfile moves. The scenario format stays as a bench input (thecorpusbench keeps its 13 scenarios), the infrastructure mutation scope keeps the scenario runner, and the 21 CREATE / CREATE2 rows the harness held are parked again under the common execution layer.benchjob is off (if: false) until the tool crates are ported; the benchmark matrix wastransactonly (the test gates series below makes itcorpus,factory,transact); the suppression-hygiene step no longer fails on an empty spec-gate plan.The revm and op-revm forks are pinned by release tag:
v40.0.3-mega.1(revm43485bf7de73b6ad92e9b5de4318eb96e2de2182) andv20.0.0-mega.2(op-revm260f65329f1f47b7412a1b2c1fda85453f66521c). Moving the pins fromrev =totag =changed only the source lines of the twelve revm packages and of op-revm inCargo.lock.Answers to the review questions
817e36e9vs260f6532.817e36e9is the branch commit of the op-revm pin pull request;260f6532is its squash merge onto op-revmmain.git diff 817e36e9 260f6532in the op-revm checkout is empty and both commits have treed3f2e5cb: same tree, different commit. The tagv20.0.0-mega.2is on the merge commit, and the merge commit is the acceptance baseline; every check below ran on the tagged graph.MegaEvm::into_inner()and the implementation aliases. None today.MegaInnerEvm,MegaInstructions,MegaInnerContextandinto_inner()are now crate-private.Commits
build: take the four tool crates out of the workspace members80d13bff, applying the test inventory to the integration testsbuild!: move to the revm and op-revm forks and take out the legacy coresrc/, examples and bench targets leave; 107 unit tests deleted, 181 parked; 11limit/*mutants suppressions droppedfeat: add MegaSpecId::SATIN, MegaHardfork::Satin and the chain activation tablefeat: carry the external environment interfaces over unchangedexternal/(byte-identical toa8f8c7c9exceptmod.rswithoutgas.rs)feat: add the provisional constants table and the shared typesconstants.rs,types.rstest: carry the test utilities overtest_utils/build: align clippy's MSRV with the workspace (1.94)clippy.tomlfeat: run Satin through op-revm's OpEvmMegaContext,MegaEvm,MegaEvmFactory, equivalence testsfeat: move the keyless deployment helpers to system/keyless as data-only modulesfeat: add the limit and access placeholders and the block executor skeletonfeat: define the crate's public surfaceunused_crate_dependenciestest: add a transact bench skeleton for Satintest: close the mutation survivors of the Satin skeletondocs: rewrite AGENTS.md for the Satin engineAGENTS.md, crate README,REVIEW.mdci: keep the satin workflows runnable without the tool cratesfix: keep ParentBudgetExceeded in the keyless error ABItest: share the zero-fee transaction setup and tighten the Satin testsdocs: fix review findings in the Satin docsci: fail the hygiene step on a grep error, not only on a planner errorbuild: pin the revm and op-revm forks by release tagrev =→tag =build: raise the MSRV to 1.94.1, the locked alloy 2.4 minimumrefactor: keep the op-revm implementation types crate-privatePrecompilesdocumented as provisionalstyle: format the crate-private instruction table aliasdocs: name the owning mechanisms in comments and docs_pending/name the owning mechanismtest: record the reviewer sign-off for the EthSpecId default suppressionstyle: drop the trailing blank lines of the parked test filesgit diff --checkcleandocs: say what lands on top of the Satin EVM wrapperdocs: make the parked-test ledger English-only and drop the internal pointers_pending/README.md: English disposition labels, a legend for the decision ids, decision notes without private pointers; counts and owners unchangeddocs: reword two comments that point at later changestest: add the JSON transaction scenario format and its MegaEvm runnertest_utils::Scenariotest: add the differential harness against stock revm 43crates/mega-differential, 23 hand-written scenarios, fee-vault registry entriestest: carry the execution-spec-test-derived corpus over to the differential harnesstest: decode delegation designators in scenario pre-states and let scenarios describe themselvestest: group accepted differences by mechanism in the deviation registrytest: cover EIP-7702, access lists, SELFDESTRUCT to existing accounts, logs and precompiles in the differential corpusci: run the differential harness as the differential checkdifferentialjobbench: add a differential-corpus slice and EVM construction to the Satin bench setcorpus,factorybenchestest: retire the parked legacy mutant killerstest: port the test gates' parked CREATE and CREATE2 tests into the differential corpusci: let the mutation driver run one shard of a large mutant setMUTANTS_SHARDdocs: write the test-gate policy into REVIEW.md and AGENTS.mdci: run the execution-spec fixtures through the fork's runner at the pinned tagexec-spec.ymlwith pinned countsdocs: keep what the retired mutant killers cited in the pending ledgertest: share the arms' record code and tighten the scenario formattest: cover gas prices in the differential corpusci: fail the execution-spec count when its runner failsdocs: keep the corpus size out of REVIEW.md and qualify the execution-spec coveragetest: verify a KZG proof in the differential corpusci: fail the execution-spec count when a counting command failsdocs: keep the static-callee halt reasons owed in the pending ledgerdocs: make the mutation reference numbers reproducibletest: add a mutation scope for the test gates' own infrastructureinfrascope, its job, the hygiene universetest: close the infrastructure mutation survivorsfeat: define the Satin gas ledgers, block counters and limit verdict typesMegaGasUsage,BlockGasCounters,LimitKind,LimitCheck,MegaLimitExceeded; 7 rows portedfeat: run Satin through MegaHandler and MegaEvm's own frame lifecycleMegaEvmover revm'sEvm,MegaHandlerover op-revm'sfeat: stage Host observations and commit write records after the opcodeJournalInspectTr; 33 rows portedfeat: carry the inherited reservoir on synthetic frame results and guard the call depthfeat: stop transactions that cross a limit with a latched revertEvmTxRuntimeLimits; 1 row portedfeat: gate block execution on declared observers and refuse creation revivalsfeat: report the transaction outcome with its ledgers, usage and stopexecute_transaction; 1 row portedtest: assert the halt reason of a creation in a static calleebench: add deep calls, storage writes and logs to the transact benchdocs: write the common execution layer's contracts into AGENTS.md and the crate READMEfix: keep the sender's account and stopped creations out of the write recordstest: reach the creator-nonce check through an inspector and a collisionrefactor: reset the layer at the EVM's entry points and share the pre-frame answerfix: keep a selfdestruct to the sender out of the write recordsfix: enforce a limit stop before the writes it guardstest: pin the limit verdicts, the Host's staging and the lane merge ruletest: cover which frame's account counts as recorded and the limit boundarytest: draw the reservoir before a stop, report history gas, and rewrite results under the latchfix: check a declared observer's inputs, stack, memory and journal writes in debug buildstest: pin fees, refunds, system calls and rejections against op-revmtest: pin the synthetic-result settlement to revm's frame returntest: name the ported tests after what they checkdocs: record the review round's rules in AGENTS.md and the frame init orderstyle: drop the trailing blank line of a parked test filegit diff --checkcleantest: pin the authority records at the data-size caprefactor: check every creation's nonce bump, the outermost one includedtest: suppress the equivalent and dead mutants of the common execution layerbench: give the corpus bench its own scenario inputscorpusbench reads, undercrates/mega-evm/benches/scenariostest: park the CREATE and CREATE2 tests again under the common execution layertest: pin the nonce and balance a scenario's database holdstest: mutate the scenario runner alone in the infrastructure scopeinfrascope, its job,mutants/README.md,REVIEW.mdnumbersbuild: take the differential harness out of the repositorydifferentialcheck, the test step's--exclude, 35 lockfile packagesdocs: say the differential harness is maintained outside this repositoryAGENTS.md,REVIEW.md, the scenario module's commentsThe legacy core cannot build on revm 40, so
build!moves the manifests and removes the legacy sources in one commit. Every commit of every series passescargo check --workspace --all-targets --locked.Dependency matrix
a8f8c7c9)revm(+ 11 sub-crates)revm-latestbench alias)v40.0.3-mega.1(43485bf7) via[patch.crates-io]op-revmop-revm-latestalias)f67d87cd, patched to megaeth-labs/op-revm tagv20.0.0-mega.2(260f6532)alloy-evmalloy-op-evmf67d87cd; its op-revm edge resolves to the forkalloy-op-hardforksf67d87cd(declared; not a core dependency yet)op-alloy-consensusf67d87cdrevm-inspectorsalloy-primitivesalloy-consensus/alloy-eipsalloy-hardforksalloy-sol-typesalloy-sol-macro*family is held at 1.6.0 in the lockfile (1.7.3 macros against 1.6.0 types breakmega-system-contracts)mega-evm,mega-system-contracts,mega-state-testThe tool-only alloy crates (
alloy-provider,alloy-rpc-*,alloy-transport*,op-alloy-network,op-alloy-rpc-types) are declared at the node's 2.x versions but not resolved: no member uses them until the tool crates are ported and rejoin the workspace.Bench targets
transactExecuteEvm::transactfor an empty transaction and an ether transfer throughMegaEvmand throughOpEvmon the sameCfgEnv; the common execution layer adds deep calls, storage writes and logsattack_replay,block_bench,comp_cost,ctt,enriched_tx,mega_bench,revm_benchbenches/common/revm-latest,op-revm-latestaliasesbenches/data/*.hex(revm_benchinputs),benches/fixtures/known_attack_deploy.json(attack_replayinput)Exported API sketch
What a consumer (the node, the tools) sees from
mega_evm:MegaSpecId#[repr(u8)] #[non_exhaustive] enum { SATIN = 12 },Default=SATIN;ALL,into_op_spec()(KARST),into_eth_spec()(OSAKA),name();Display/FromStr"Satin"; serde"SATIN";From<MegaSpecId>forOpSpecId,EthSpecId,&'static strParseMegaSpecErrorLegacy(String)forEquivalence…Rex7(LEGACY_SPEC_NAMES),UnknownotherwiseMegaHardforkhardfork!enum{ Satin },spec_id()MAINNET_CHAIN_ID,TESTNET_CHAIN_ID,ChainActivation { chain_id, satin: Option<BlockTimestamp> },CHAIN_ACTIVATIONS,chain_activation(chain_id)satinisNoneon both chainsMegaContext<DB, ExtEnvs = EmptyExternalEnv>new(db, spec),new_with_external_envs(db, spec, envs),with_cfg(CfgEnv<MegaSpecId>),with_block,with_tx,with_chain,modify_chain,spec(),mega_cfg(),external_envs(),into_parts(); from the common execution layer,with_tx_runtime_limits(EvmTxRuntimeLimits),additional_limit()and, withtest-utils,additional_limit_mut(); implements revmContextTr(Cfg = CfgEnv<OpSpecId>),ContextSetters,HostMegaEvm<DB, INSP, ExtEnvs = EmptyExternalEnv>new(ctx),with_inspector(i),ctx(),ctx_mut(),is_inspecting(); from the common execution layer,with_trusted_inspector(i),has_rewriting_inspector(),inspector(),execute_transaction(tx), and revm'sEvmTr/InspectorEvmTr; implementsExecuteEvm,ExecuteCommitEvm,InspectEvm,InspectCommitEvm,SystemCallEvm,InspectSystemCallEvm(revm) andalloy_evm::Evm(Spec = MegaSpecId,Precompiles = OpPrecompiles, documented as provisional); the revm traits reportEVMError<DB::Error, OpTransactionError>, the alloy-evm interfaceEVMError<DB::Error, MegaTransactionError>MegaEvmFactory<ExtEnvFactory = EmptyExternalEnv>new(),external_env_factory(),with_external_env_factory(f); implementsalloy_evm::EvmFactory(Precompiles = OpPrecompiles, documented as provisional)MegaTransaction=alloy_op_evm::OpTx,MegaTransactionError=alloy_op_evm::OpTxError,MegaHaltReason=op_revm::OpHaltReason,MegaTxType,MegaTxEnvelopeMegaHandler,MegaGasUsage,MegaTransactionOutcome,BlockGasCounters,LimitKind,LimitCheck,LimitUsage,MegaLimitExceeded,EvmTxRuntimeLimits,AdditionalLimit,StagedRecord,JournalInspectTr,TrustedObserver,DeclaredObserver,FORBIDDEN_CREATE_REVIVAL,untouched_call_gas,untouched_create_gas,with_pools_of,synthetic_frame_result,settle_frame_result,WRITE_RECORD_SIZE,LOG_BASE_SIZE,LOG_TOPIC_SIZEMegaBlockExecutor<E>new,evm,evm_mut,into_evm; block execution lands with the block executorExternalEnvFactory,ExternalEnvTypes,ExternalEnvs,SaltEnv,OracleEnv,EmptyExternalEnv,BucketId,MIN_BUCKET_SIZEconstants::{COST_PER_STATE_BYTE, SLOT_STATE_GAS, ACCOUNT_STATE_GAS, COST_PER_HISTORY_BYTE, TX_GAS_LIMIT_CAP, TX_DATA_LIMIT, BLOCK_DATA_LIMIT}system::keyless::{decode_keyless_tx, recover_signer, calculate_keyless_deploy_address, KeylessDeployError, encode_error_result, decode_error_result}decode_keyless_txtakes no spec and always rejects trailing bytes;KeylessDeployErrorcovers everyIKeylessDeployerror, withParentBudgetExceeded.kindas the raw ABIu8test_utils(featuretest-utils)MemoryDatabase,ErrorInjectingDatabase,BytecodeBuilder,GasInspector,right_pad_bytes,transact(spec, db, caller, callee, data, value, gas_limit),zero_fee_l1_block_info(),op_transaction(tx),TestExternalEnvs; from the test gates series, the JSON scenario format (Scenario,PreAccount,ScenarioTx,ScenarioTxKind,AccessListEntry,AuthorizationEntry,ScenarioTxOutcome) withvalidate,database,blockandrunonMegaEvmrevm,op_revm,alloy_evm,alloy_op_evm,alloy_primitives,alloy_hardforks,alloy_consensus,alloy_sol_types,op_alloy_consensus; aliasesSpecId,Transaction,HaltReason,TransactionError,TxType,Evm,EvmFactory,Context,BlockExecutorThe op-revm types a
MegaContext/MegaEvmwraps (MegaInnerContext,MegaInnerEvm,MegaInstructions) andMegaEvm::into_inner()are crate-private, so no consumer can reach the concrete OP engine or its instruction layout. The one place the OP implementation stays visible is thePrecompiles = OpPrecompilesassociated type that alloy-evm'sEvmandEvmFactoryrequire: its doc comment says it is provisional and that code namingOpPrecompilesthrough it has no source-compatibility promise when the Satin precompile provider replaces it.The inspector entry is revm's
InspectEvm/InspectSystemCallEvmonMegaEvmplus alloy-evm'sset_inspector_enabled; a test runs revm-inspectors'TracingInspectorthrough it. The common execution layer adds the admission gate (with_trusted_inspector,has_rewriting_inspector,DeclaredObserver).Measured on this tree (Osaka table, no MegaETH pricing yet): empty call 15,000 gas, value transfer to an empty account 21,000, one SSTORE 37,106; state gas 0 in all three because the Osaka table has no state-gas entries (the Satin gas table adds them). A 201,000,000-gas empty call leaves exactly 1,000,000 in the reservoir.
Test inventory reconciliation
_pendingcrates/mega-evm/tests/crates/mega-evm/src/The 36 ported unit tests: 17 of the 21 the inventory assigns to the Satin skeleton, 5 bucket-hasher tests (owned by SALT pricing, carried with
external/), 14 keyless helper tests (owned by native keyless deployment, carried withsystem/keyless). The other 4 skeleton rows are parked under the mechanism they need (the common execution layer, the Satin gas table, SALT pricing, the block executor), with the reason in the README. 18 new tests were added. The classification is the inventory's own rule table applied to every test name; the per-mechanism counts match the inventory's totals exactly before the ports.What
_pending/holdsDecision ids (
Dnn) index the Satin decision table, the numbered list of design decisions behind this engine; it is published with the engine's specification.As of the test gates series (the skeleton series left 836: the test gates retired the 44 parked legacy mutant killers and ported their own 21 rows into the differential corpus):
tests/src/The common execution layer then ports its 41 rows and 4 depth-guard rows of the system contract interceptors: 726 remain, and the README lists the ported rows. The harness removal parks the test gates' 21 CREATE / CREATE2 rows again, under the common execution layer: 747 remain.
_pending/has nomain.rs, so Cargo never builds it. It no longer has amutation/directory: the legacy mutant killers were retired (the README keeps what they cited), since the Satin sources leave no survivor to regenerate one for.Decisions taken in this series
MegaSpecId::SATIN = 12: the discriminant continues after the legacy ladder, so a rawas u8never aliases a legacy spec.ParseMegaSpecError::Legacyis distinct fromUnknown, so the ported tools can route a legacy fixture to the legacy engine instead of failing generically.MegaContext::with_cfgoverwrites the gas table, the 8037/2780/7708 switches, the cap and the margin whatever the caller passes (reth's Osaka env builder would otherwise set the EIP-7825 cap).MegaTransactionis alloy-op-evm'sOpTx(orphan rules keep alloy-evm's traits offOpTransaction;OpTxalready carries the signed-tx conversions the executor needs), so alloy-op-evm is a core dependency.OpPrecompiles: alloy-evm'sPrecompilesMaponly implementsPrecompileProviderfor revm's concreteContext, not for a wrapping context. The Satin gas table brings the Satin precompile provider (KZG 100k) and decides the dynamic-precompile shape the node's RPC needs; the associated type is documented as provisional, with no source-compatibility promise for consumers that name it.KeylessDeployError::ParentBudgetExceededkeeps its ABI shape with the rawu8kind: the legacyLimitKindis gone, and the Satin resource dimensions are defined by the limit mechanisms that follow. (The common execution layer bringsLimitKindback with the legacy discriminants; the error keeps its rawu8.)derive_morebecame an optional dependency oftest-utils.Test plan
cargo metadata --locked --no-depscargo metadata --locked(resolved graph)rust_versionis 1.94.1 (alloy 2.4.2), equal to the workspace'scargo +1.94 check --workspace --all-targets --all-features --locked(rustc 1.94.1)cargo tree -i revm --lockedmegaeth-labs/revm?tag=v40.0.3-mega.1#43485bf7; op-revm 20.0.0 frommegaeth-labs/op-revm?tag=v20.0.0-mega.2#260f6532; all 12 revm crates and op-revm from the tags;revm@27.1.0matches no packagecargo fmt --all --checkcargo clippy --workspace --lib --examples --tests --benches --all-features --lockedcargo sort --check --workspace --grouped --order …cargo test --workspace --lockedcargo check -p mega-evm --target riscv64imac-unknown-none-elf --no-default-features --lockedforge buildincrates/system-contracts;cargo build -p mega-system-contractsgit diff origin/satin -- bin crates/mega-state-test crates/state-testgit diff --check origin/satin..HEADgrep -rnP '[\x{4e00}-\x{9fff}]'over the treegrep -rnP '\bT[0-9]+(\.[0-9]+)?\b'over*.rs,*.md,*.toml,*.yml,*.yaml,*.py,*.sh,*.jsonnpx prettier --check '*.md' 'docs/**/*.md'cargo bench -p mega-evm --bench transactcargo check --workspace --all-targets --lockedat every commit of the seriescargo mutants --list --package mega-evm+umutate.py plan --packs spec-gate+mutation_gate.py orphans(the hygiene job's steps, with comby and universalmutator installed)scripts/mutation_test.sh diff a8f8c7c9+mutation_gate.py report --suppressions mutants/suppressions.tomlEthSpecIddefault entry), 65 unviableThe last two commits change only
crates/mega-evm/tests/_pending/README.md, which no Cargo target, formatter or linter reads, and the wording of two comments. fmt, test,git diff --check, the two greps andcargo checkran on the head, prettier ran on the README commit, and the other rows ran on the commit before the two.Open items for review
mega-evmnow depends on git-only crates (op-revm,alloy-op-evm,op-alloy-consensus) and on[patch]redirects, socargo publishof 2.0.0-alpha.1 would fail. Crate identity and publishability under patches belong to the crate identity and publishing work; nothing here setspublish = false.CI notes
/benchmark,/replay-bench, nightly mutation) run the default branch's copy of the workflow file. OnsatinPRs,/replay-benchand/benchmarktherefore still try the legacy targets and fail at build until the tool crates are ported and the test gates rebuild the benchmark suite;mainis not touched from here.mutation.yml's PR-diff cargo-mutants gate covers the whole new core on this PR; the local run above takes about three minutes.Test gates
The second commit series lands four gates: the differential harness, the instruction-count baseline, the interim execution-spec run and the mutation-testing rules. The fourth series takes the differential harness out of the repository again (section "Differential harness removal"); the other three stay, and the numbers below are updated where it moved them.
Facts in the sections above that this series changed: the Summary (its test-gates bullet and the benchmark matrix in its CI bullet), the
test_utilsrow of the exported API sketch, and the table under "What_pending/holds" with the sentence after it.Differential harness
Removed from this repository by decision: the harness (runner, registry and scenario corpus) is maintained outside mega-evm and is not a check on its pull requests, and the scenario format (
test_utils::Scenario) stays as thecorpusbench's input.Instruction-count baseline (CodSpeed)
transact(kept),corpus(13 JSON scenarios throughMegaEvm, since the harness removal undercrates/mega-evm/benches/scenariosand described as a bench input, not a conformance suite: SSTORE, create, nested revert, two transactions, system call, calldata floor, nested refund at depth 10, EIP-7702 delegation, access list, SELFDESTRUCT from init code, logs, ecrecover, modexp; each checks that no transaction is rejected before it is measured) andfactory(MegaEvmFactory::create_evmandcreate_evm_with_inspector).codspeed.ymlbuilds every bench target ofmega-evm, so it picks them up unchanged;benchmark.yml's matrix is[corpus, factory, transact].cargo bench -p mega-evm --bench corpus,--bench factoryand--bench transactexit 0 (wall-clock, noisy: corpus 1.4–20 µs per scenario, factory 0.8 µs, transact 1.7–1.8 µs).REVIEW.md, "Test gates"). The baseline is the latestsatinpush; regressions are read from the CodSpeed report on the pull request. The regression threshold is a setting of the repository's CodSpeed project: it is not in the repository (no.codspeedfile, nothing incodspeed.yml) or in the check output, which reports "Performance Gate Passed" or "Performance Regression: <overall %>" without a threshold, so the policy does not state a number. The CodSpeed check is not required onsatin(the required checks arelint,test,no-stdandrequire-label).Execution-spec tests
.github/workflows/exec-spec.ymlreads the fork tag fromCargo.lock(and fails unless the lockfile pins exactly one fork revision and the tag names it), checks out megaeth-labs/revm at that tag with the fork's own toolchain, runs./scripts/run-tests.sh clean cargo release, then counts the Osaka and Amsterdam state tests the runner executes (from its per-test JSON outcome) against the post-states the fixtures define, and compares them with the pinned numbers. Triggers:workflow_dispatch, a weekly schedule (inert untilsatinis the default branch), and pull requests and pushes tosatinthat touchCargo.tomlorCargo.lock. 60-minute bound; not required. The script prints no counts, so the counting step runs the two state-test trees once more with the runner's per-test JSON outcome (about a minute, on the script's build). The comparison runs under||, where errexit is off, so each of the three commands behind it is guarded on its own: the runner, the count of the state tests the fixtures define, and the count of those the runner executed. Probed by sourcing the workflow'scheckwith stubs: it returns 0 with all three succeeding, and 1 when any one of them prints the expected number and then exits non-zero, or when a count is off by one.First run (local, at
v40.0.3-mega.1, fixtures mainv5.4.0develop andtests-glamsterdam-devnet@v8.1.0): the script exits 0 (every state-test suite passes; main blockchain tests 61,606 passed, 76 skipped), 22 minutes wall, most of it downloading 12 GB of fixtures.The skipped ones are the create-collision fixtures with storage the runner skips by name, and expected-exception transactions it cannot build (bad signatures, blob-hash creates).
REVIEW.mdsays that no check in this repository runs execution-spec tests onMegaEvmitself until the state-test tool is ported.Mutation testing
scripts/mutation_test.sh diff a8f8c7c9: the same 115 / 49 / 0 / 1 / 65, PASS, 4m12s (JOBS=8).REVIEW.mdnow names that base commit and the driver-plus-gate command pair, becausecargo mutantson its own generates a different population (the driver excludes function-scoped suppressions before generation, the gate filters line-scoped ones after the run).scripts/mutation_test.sh diff 04a16eecmutates nothing: the onlysrc/change istest_utils/, which.cargo/mutants.tomlexcludes, so the gate reports no mutatable change.test_packageofmega-evmalone, are why the code implementing the gates was never mutated..cargo/mutants-infra.tomlplusscripts/mutation_test.sh inframutate the scenario runner (crates/mega-evm/src/test_utils/scenario.rs) and the harness'srecord,diffandregistrymodules against both packages' tests, so the whole corpus is among the killing tests; the production scope is unchanged and the two are disjoint. First run: 80 mutants, 58 caught, 18 survived, 4 unviable. Fifteen survivors were real gaps — the rendering of a log and of an account (what the comparison reports and what a registry effect is a pattern over), the glob's middle segments, that an entry names both a mechanism and a reason, the per-kind field rules and the system-call shape of a scenario transaction, and the fixed block — and are closed by six tests. Three are equivalent (Scenario::blockwrites a timestamp, a base fee and a gas limit that revm'sBlockEnv::default()already holds) and are suppressed as line entries, killable again the moment an upstream default moves, since the new test pins all five values. The run is now 80 mutants, 73 caught, 0 survived, 3 suppressed, 4 unviable, PASS, 7m46s (JOBS=8). Jobcargo-mutants infrastructureruns it on a pull request touching that code, either configuration, the driver or the gate, and nightly; 90-minute bound, not required. A suppression can belong to either scope, so the suppression-hygiene job lists both into its universe (331 mutants then; all 14 suppressions matched a live mutant). With the harness gone (section "Differential harness removal"), the scope is the scenario runner alone, tested bymega-evm: 39 mutants. Two of them survived,Scenario::databasedropping a pre-state account's nonce or its balance, which only the corpus had killed; a unit test kills both, and the run is 39 mutants, 34 caught, 0 survived, 3 suppressed, 2 unviable, PASS, 5m45s (JOBS=8). The hygiene universe is now 551 mutants, and all 18 suppressions match a live mutant.REVIEW.md: shard a series whose diff lists more than 1,000 mutants (about an hour at the reference rate; the job bound is 330 minutes).scripts/mutation_test.shgainsMUTANTS_SHARD=k/n(cargo-mutants'--shard), becausefile <glob>is not diff-scoped; checked onconstants.rs: 20 mutants split into two disjoint shards of 10, each gated PASS. The pull request that first needs it adds a shard matrix to the job.REVIEW.mdsays it finds nothing on Satin, which it now does.mutation/directory held 5 files (4 test files andmain.rs), 44 tests, all keyed to surviving mutants of the legacy sources and owned by mechanisms Satin does not have yet. The Satin sources have no survivor (the whole-core run above), so nothing was regenerated; the files are deleted, and the pending README counts them in one line instead of listing them.The pending README is still generated from the test inventory, now with the retired mutation rows and the rows ported into the harness as inputs, and regenerating it reproduces the committed file.
_pending/now holds 771 tests (from 836).Scheduled workflows
GitHub fires a
scheduleonly from the default branch's copy of a workflow, sosatin's schedules (nightly mutation, weekly benchmark, replay-bench, doc-audit, the weekly execution-spec run) stay inert untilsatinis the default branch, and asatinschedule cannot be added from here. All five haveworkflow_dispatch, andgh workflow run <workflow>.yml --ref satinrunssatin's copy onsatin's head, so norefinput was added;REVIEW.mdlists what each does when dispatched onsatin(replay-bench does nothing until the state-test tool is ported).Test gates test plan
As run at the head of the test gates series; the
mega-differentialrows describe the crate the fourth series removes, and the final head's checks are under "Differential harness removal".cargo fmt --all --checkcargo clippy --workspace --lib --examples --tests --benches --all-features --lockedcargo sort --check --workspace --grouped --order …cargo test --workspace --lockedcargo test -p mega-differential --locked -- --nocapturecargo check -p mega-evm --target riscv64imac-unknown-none-elf --no-default-features --lockedcargo bench -p mega-evm --bench corpus/factory/transactcargo tree -i revm --locked -p mega-evmmegaeth-labs/revm?tag=v40.0.3-mega.1#43485bf7cargo tree -p mega-differential -i revm --lockedrevm@40.0.3,revm@43.0.0(both sources)scripts/mutation_test.sh diff 04a16eec+mutation_gate.py reporttest_utils/changed)scripts/mutation_test.sh diff a8f8c7c9+mutation_gate.py reportscripts/mutation_test.sh infra+mutation_gate.py reportJOBS=8)mutation_gate.py orphansover both scopes' universecheckfunction, sourced with stub counting commandsactionlintondifferential.yml,exec-spec.yml,mutation.yml,build-and-test.yml,benchmark.ymlblacksmith-*runner labels, whichsatin'sbenchmark.ymlalready hasv40.0.3-mega.1=43485bf7; counts as in the table above; a wrong pinned count and a failing runner each fail the step with an error linenpx prettier --check '*.md' 'docs/**/*.md'git diff --check origin/satin..HEADcrates/mega-evm/tests/_pending/README.mdfrom the test inventorycargo check --workspace --all-targets --lockedandcargo test -p mega-differential --lockedat every commit of the seriesReview round
A two-axis review (standards, spec) of the series found no breach of a documented standard; the follow-up commits:
TxSpecbecameScenarioTx.A second, zero-context review of the same series returned three findings, all confirmed; the follow-up commits of the two that did not concern the harness alone:
checkruns under||, where errexit is off, so the two command substitutions behind the comparison were unguarded: a pipeline printing the expected number and then exiting non-zero still passed. Both are guarded now, and the fix is probed by sourcing the workflow's owncheckwith stub counting commands.The reviewer also asked whether the six static-callee halt-reason assertions dropped by the ported CREATE tests are discharged. They were not:
_pending/README.mdrecorded them as owed, and the common execution layer re-adds them (tests/satin/static_callee.rs). They stay when the harness leaves and the CREATE rows are parked again.Common execution layer
The third commit series defines the contracts every later mechanism plugs into: the frame lifecycle, the result and gas types, the observe-stage-commit layer with its write records, the abort protocol, the inspector entry and the synthetic-result reservoir contract. It adds no pricing: with no limit configured,
MegaEvmstill equals op-revm (the equivalence tests). The layer was certified against stock revm 43 before the differential harness left the repository: at the head of this series the harness ran 352 scenarios with 0 unexplained differences and 0 stale registry effects, the same line as before the series, and the registry unchanged.Facts in the sections above that this series changed: the Summary (its opening sentence, the Core bullet and a new bullet for this series), the
transactrow of "Bench targets", theMegaContext,MegaEvmand alias rows of the exported API sketch with the new rows under it and the inspector paragraph after it, the sentence after the table under "What_pending/holds", theKeylessDeployErrorline of "Decisions taken in this series", and the last paragraph of the test gates' review round.The contracts
MegaHandler<EVM, ERROR, FRAME>wraps op-revm'sOpHandlerand overridespre_execution(applied EIP-7702 authorities) andlast_frame_result(the outermost lane, the latch translation, op-revm's settlement, the history gas);MegaEvmimplementsEvmTr/InspectorEvmTritself:frame_init(latch → depth guard →MegaEvm::intercept→MegaEvm::rewrite_keyless→ lane and records → revm),frame_run/inspect_frame_run(before_frame_run: a latched frame returns the stop without an instruction),frame_return_result(lane pop, latch rewrite),inspect_frame_init(pairedframe_start/frame_endon every early return)evm/execution.rsMegaGasUsage { regular, state, history, reservoir_remaining, floor, gas_used }withnew(&ResultGas, history)andblock_execution_gas();MegaTransactionOutcome { result_and_state, gas, usage, limit_exceeded }(derefs toResultAndState) fromMegaEvm::execute_transaction;BlockGasCounters { execution, state, history }withrecord(&MegaGasUsage)evm/result.rs,block/result.rsStagedRecord::{Sstore(SStoreResult), Log { topics, data_len }, SelfDestruct { had_value, target_exists, to_other_account, beneficiary }};AdditionalLimit::{stage_record, commit_staged_record, discard_staged_record, discard_stale_record}(crate-internal: the Host and the wrappers are their only callers) and the publicstaged_record(); wrappers forSSTORE,LOG0..LOG4,SELFDESTRUCTinmega_instructions;LimitUsage { data_size, write_records },WRITE_RECORD_SIZE= 40,LOG_BASE_SIZE=LOG_TOPIC_SIZE= 32;JournalInspectTr::{inspect_account, inspect_account_code_hash, inspect_storage}for reads that do not warmevm/host.rs,evm/instructions.rs,limit/LimitKind::{DataSize, KVUpdate, ComputeGas, StateGrowth}(discriminants 0–3, as the legacy engine encoded them);LimitCheck::{WithinLimit, ExceedsLimit { kind, limit, used, frame_local }, Exempt}withrevert_data();MegaLimitExceeded(uint8 kind, uint64 limit);AdditionalLimit::latch(kind, limit, used)andlatched();EvmTxRuntimeLimits { tx_data_size_limit, frame_data_size_limit }(both unlimited by default) viaMegaContext::with_tx_runtime_limitslimit/MegaEvm::with_inspector(tools),with_trusted_inspector<I: TrustedObserver>,has_rewriting_inspector();TrustedObserver(implemented forNoOpInspector,&mut T,DeclaredObserver<I>);DeclaredObserver<I>forwards and, in debug builds, checks every callback;FORBIDDEN_CREATE_REVIVALevm/inspector.rs,evm/mod.rsuntouched_call_gas(&CallInputs),untouched_create_gas(&CreateInputs),with_pools_of(gas_limit, &Gas),synthetic_frame_result(&FrameInput, InstructionResult, Bytes),settle_frame_result(ctx, &mut GasTracker, &mut FrameResult)evm/frame.rsInvariants
sstore,logandselfdestructstage what revm hands them and record nothing; every other Host method delegates.SELFDESTRUCT's ownSelfDestructresult) or discards it when the opcode failed. AnSSTOREthat runs out of gas after its Host call wrote the slot counts nothing. Every other opcode, and the static gas table, is revm's.SELFDESTRUCTmoving value to another account, an applied EIP-7702 authority, the transaction's value recipient or created account. The sender's account is part of the transaction body and never a record. Deduplicated per frame, on the frame's lane: a success merges the lane into the caller's, a failure discards it; a creator's nonce record survives a failed creation once the nonce was bumped. The KV count is the write-record count.MegaLimitExceeded: no caller resumes, no frame starts, every returned result is rewritten to the stop, and the outermost frame settles like an EIP-8037 revert (unspent regular gas and the whole reservoir back). A frame budget reverts its frame alone. A limit is enforced before the writes it guards: a frame whose start would cross it is answered before revm builds it (a creation still bumps its creator's nonce, so a stopped creation transaction cannot be replayed), and EIP-7702 authorities whose records would cross it are taken back with their gas. Real out-of-gas, precompile out-of-gas and invalid opcodes still halt and burn; an out-of-gas before the first frame takes back what pre-execution counted.Gas::new(limit); they carry the calling opcode's upfront state-gas flags and settle exactly like revm's own. The depth guard and the latch exit use them here; interceptors, precompile normalisation and native keyless deployment later.with_trusted_inspector. The one refused rewrite is a failed creation turned into a success.MegaEvmresets the layer before it runs the handler.Halt-reason set
MegaHaltReasonstaysop_revm::OpHaltReason:Base(HaltReason)for the EVM's own halts andFailedDeposit. A resource limit never halts a transaction; it reverts withMegaLimitExceeded, andMegaTransactionOutcome::limit_exceededsays which limit (a contract can revert with the same bytes).What
types.rsstill aliasesAll five, because Satin adds nothing to them:
MegaTransaction(OpTx),MegaHaltReason(OpHaltReason),MegaTransactionError(OpTxError),MegaTxType,MegaTxEnvelope. The types Satin does add are new names (MegaGasUsage,MegaTransactionOutcome,BlockGasCounters,LimitCheck,MegaLimitExceeded).Ported rows
All 41 rows the pending README assigned to this layer, plus the 4 depth-guard rows of
rex5/call_too_deep_guard.rsthat the README assigned to the system contract interceptors and that need no interceptor (the synthetic-result contract they pin lands here)._pending/holds 726 tests (from 771), and the README lists the ported rows with where each went.src/evm/result.rssrc/evm/result.rs(rewritten for the Satin halt set)src/limit/mod.rssrc/limit/mod.rssrc/limit/frame_limit.rssrc/limit/frame_limit.rs(the data-size and write-record lanes)src/evm/host.rssrc/evm/host.rs(JournalInspectTr)src/evm/mod.rstests/satin/outcome.rsequivalence/evm_state.rstests/satin/state.rsrex4/eip7702_delegation_cycle.rstests/satin/state.rsmini_rex/db_error.rstests/satin/db_error.rs(a database error now surfaces asEVMError::Database, notCustom)rex5/frame_target_updated_dedup.rstests/satin/write_records.rsrex6/create_frame_accounting.rstests/satin/write_records.rsrex6/self_transfer_account_dedup.rstests/satin/write_records.rsrex5/call_too_deep_guard.rstests/satin/synthetic_frame_gas.rs(the depth guard answers without an interceptor; a latched transaction's stop wins overCallTooDeep)The six static-callee halt-reason assertions the test gates left owed are back in
tests/satin/static_callee.rs: they read the callee's frame outcome as its caller receives it, and every one isStateChangeDuringStaticCall, as in canonical revm.When the harness left the repository, the 21 CREATE / CREATE2 rows it had held as scenarios were parked again under this layer (section "Differential harness removal"), so
_pending/holds 747 tests on the final head; the six assertions above stay.Bench
transactgainsdeep_calls(a contract calling itself 64 deep, each frame writing a slot and logging),storage_writes(200 first writes, then 200 write-backs) andlogs(200 two-topic logs), each throughMegaEvmand throughOpEvm, so CodSpeed reports the frame lifecycle's and the wrappers' cost on this pull request. Local wall-clock (noisy, for orientation only): deep calls 25.5 µs vs 25.2 µs, storage writes 20.4 vs 19.2, logs 7.6 vs 7.2, empty transaction 1.80 vs 1.57.Mutation testing
Production scope,
scripts/mutation_test.sh diff daed59c8+scripts/mutation_gate.py report --suppressions mutants/suppressions.toml:docs: write the common execution layer's contracts into AGENTS.md and the crate README(the ten feature commits)docs: record the review round's rules in AGENTS.md and the frame init order(the review round)test: suppress the equivalent and dead mutants of the common execution layer(the head of the series)evm/execution.rs, 16 inlimit/limit.rs, 10 inevm/inspector.rs, the rest in the Host, the frame lanes, the verdicts, the context, the synthetic results and the result types) are closed by the review round's pins.record_applied_authorities'>→>=(authority records landing exactly on the cap), closed bytest: pin the authority records at the data-size cap. Two were equivalent: the creator-nonce check'sdepth > 0guard (the outermost creation has no creator record to take back);refactor: check every creation's nonce bump, the outermost one includedremoves the guard. The other five are suppressed.mutants/suppressions.toml, each saying when it becomes killable. Dead while EIP-7708 is off (frame init journals no log, and op-revm's precompiles journal none): the twologs().len() != logs_icomparisons ofinspect_frame_init,inspect_logs, andDeclaredObserver::log(an opcode's log reacheslog_full, which a test covers); they become live when EIP-7708 is switched on. Equivalent:AdditionalLimit::discard_stale_record— each wrapped opcode stages its own record through the Host whenever it completes and a failing opcode's wrapper discards what is staged, so a stale record can never be committed; the entry discard guards a future wrapped opcode that completes without a Host call.Shapes chosen where another was plausible
MegaEvmowns revm'sEvmrather than op-revm'sOpEvm:OpEvm's frame methods cannot be overridden, and the frame lifecycle is the extension point.MegaHaltReasonstays an alias: stops are reverts, so a Satin halt enum would be op-revm's with a new name.LimitKindkeeps the legacy discriminants, so contracts decodingMegaLimitExceededkeep working.SSTORE,LOGandSELFDESTRUCT; value-transfer and creation records are counted at frame init, which runs after the opcode completed.CALLandSTATICCALLonly: those are what an interceptor answers; revm checks the depth of every frame it builds.MegaEvm's entry points, not in copies of revm's handler defaults.DeclaredObserver's debug check covers what it can see cheaply (gas, action, stack depth, memory size, inputs, results, journal entries and logs); it is documented as a tripwire.Review round
A standards review and a spec review ran over the series once its ten feature commits were in, and two mutation runs followed; the later commits are their fixes.
SELFDESTRUCTwhose beneficiary is the sender records nothing.MegaEvm's entry points (transact_one,replay,inspect_one_tx, both system-call paths) rather than in copies of revm's handler defaults, and the latch and the depth guard share one pre-frame answer, so an inspector's answer can neither start a frame of a stopped transaction nor reach past the call-stack limit.DeclaredObserverchecks more in debug builds: the inputs, the stack, the memory size and the journal writes each callback leaves, besides gas, action and results.AGENTS.mdrecords the review round's rules and the frame init order.Common execution layer test plan
cargo check --workspace --all-targets --lockedat every commit of the seriescargo test -p mega-differential --lockedat every commit of the series that touchescrates/mega-evm/src-- --nocapture)differential: 352 scenarios, 13296 fields compared, 1088 deviations matched (op-fee-vault-touch x1080, op-karst-bn254-pairing-input-bound x8), 0 unexplained, 0 stale registry effects, the line of the test gates seriesscripts/mutation_test.sh diff daed59c8+mutation_gate.py report --suppressions mutants/suppressions.tomlat the head of the seriescargo bench -p mega-evm --bench transact,corpus,factoryThe workspace-wide checks (fmt, clippy, sort, the test suites, the no_std check, prettier,
git diff --check, the sweeps, the pending README) ran on the final head of this pull request; see "Differential harness removal".Differential harness removal
The fourth commit series takes the differential harness out of this repository: by decision the harness (runner, registry and scenario corpus) is maintained outside mega-evm, and nothing here decides where it goes. The common execution layer was certified against it before it left (section "Common execution layer").
Facts in the sections above that this series changed: the Summary (its opening sentence, the test-gates and common-execution-layer bullets, and a new bullet for this series), the commits table and the sentence after it, the sentence after the table under "What
_pending/holds", the test gates' introduction, its "Differential harness", "Instruction-count baseline", "Execution-spec tests" and "Mutation testing" subsections, the note above its test plan and its review round, and the common execution layer's opening paragraph, ported rows and test plan.crates/mega-differential(the runner, the 352-scenario corpus,deviations.json, the claim tests), its workspace membership,.github/workflows/differential.yml(thedifferentialcheck), and the--exclude mega-differentialofbuild-and-test.yml's test step, which iscargo test --workspaceagain.AGENTS.mdloses the harness's commands, its workspace row, its test-organization entry and its rule, and thecargo treerule is back to "exactly one revm".REVIEW.mdsays in three lines that the harness is maintained outside this repository and is not a check on its pull requests, and no longer counts derived scenarios as execution-spec coverage. Nothing indocs/spec/changes.test_utils::Scenario). Thecorpusbench reads its 13 scenarios fromcrates/mega-evm/benches/scenarios/, copied unchanged from the corpus, and its doc says they are a bench input, not a conformance suite.rex6/error_paths.rs), and the pending README lists the rows under the common execution layer, marking the six whose static-callee halt reasontests/satin/static_callee.rsalready asserts._pending/holds 747 tests (from 726).mega-evm(.cargo/mutants-infra.toml,scripts/mutation_test.sh infra, thecargo-mutants infrastructurejob and its path filter,mutants/README.md,REVIEW.md); its three signedBlockEnvsuppressions stay. Its first run found two survivors that only the corpus had killed,Scenario::databasedropping a pre-state account's nonce or its balance, and a unit test kills both. On the final head: 39 mutants, 34 caught, 0 survived, 3 suppressed, 2 unviable, PASS, 5m44s (JOBS=8). The production scope is unchanged. Suppression hygiene over both scopes: 551 mutants, all 18 suppressions match a live mutant.mega-differential;revm43.0.0 and its eleven 43.0.x sub-crates; the arkworks 0.6 crates (twelve);foldhash0.1.5,phf_generatorandphf_macros0.14.0,ripemd0.2.0 andsha20.11.0;tracing-subscriberwithnu-ansi-term,sharded-slab,thread_localandtracing-log. No remaining package changes version:c-kzgstays at 2.1.8 andtracing-coreat 0.1.36, the releases the harness's resolve had moved them to, so the core's KZG backend is still c-kzg 2.1.8.cargo tree -i revm --lockedshows one revm, the fork, andCargo.lockhas onename = "revm".Final test plan
On the final head of this pull request:
cargo fmt --all --checkcargo clippy --workspace --lib --examples --tests --benches --all-features --lockedcargo sort --check --workspace --grouped --order …cargo test --workspace --lockedcargo check -p mega-evm --target riscv64imac-unknown-none-elf --no-default-features --lockedcargo bench -p mega-evm --bench corpus/transactcargo tree -i revm --locked;grep -c 'name = "revm"' Cargo.lockmegaeth-labs/revm?tag=v40.0.3-mega.1#43485bf7; 1npx prettier --check '*.md' 'docs/**/*.md'git diff --check origin/satin..HEADactionlintonbuild-and-test.yml,exec-spec.yml,mutation.ymlscripts/mutation_test.sh infra+mutation_gate.py reportJOBS=8)cargo mutants --listover both scopes +mutation_gate.py orphanscrates/mega-evm/tests/_pending/README.mdfrom the test inventorycargo check --workspace --all-targets --lockedat every commit of the seriesLabels
spec:new,api:breaking,comp:core,comp:misc,dependencies,agent