Repository navigation
Releases: wighawag/revm-wasm
Release list
revm-wasm@0.4.0
Minor Changes
-
97cbc6c: Add
blobGasUsedandblobGasPriceto the execution outcomeThe artifact already supported EIP-4844 blob transactions correctly:
blobVersionedHashesandmaxFeePerBlobGasare accepted on the execute
options,BLOBHASHandBLOBBASEFEEwork, versioned-hash validation happens,
and the differential corpus covers thousands of blob transactions. What was
missing was purely a reporting gap: the outcome blob carried status, gas
used, total gas spent, refunded, return data, logs, the conditional logs bloom,
the per-account change set and the effective gas price, and nothing about blob
gas. A consumer executing a type-3 transaction could not fillblobGasUsedor
blobGasPriceon the receipt it hands its own callers, and its only honest
options were to refuse blob transactions or return a receipt it knew was
incomplete.This closes that gap. The outcome now carries two trailing fields:
blobGasUsed— revm's ownTransaction::total_blob_gas, i.e. the
versioned-hash count times the per-blob gas constant (GAS_PER_BLOB). Zero
for a non-blob transaction.blobGasPrice— revm's ownBlock::blob_gasprice, i.e.
calc_blob_gaspriceover the block's excess blob gas (thefake_exponential
rule the engine charges blob gas with, not a second implementation in
JavaScript). Zero for a non-blob transaction.
Both are computed inside the artifact from revm's own API surface, so a
consumer reads them straight offOutcomewithout reimplementing EIP-4844 fee
arithmetic.embedded-eth-node, which puts receipts on an EIP-1193 surface,
previously worked around this gap by refusing blob transactions; it can now
stop.Outcome format version bumped to 4. The two fields are appended after the
effective gas price, the head offsets are unchanged, anddecodeOutcomeis
updated. The existing differential corpus still passes byte for byte against
native revm.The two fields are written unconditionally (zero for a non-blob
transaction) rather than made conditional on the versioned-hash count. The
256-byte logs bloom is conditional on a non-zero log count, and that
conditionality is the thing the format's own documentation names as what bites
a hand-rolled decoder: it works on exactly the calls that are easiest to test
with and then desynchronises on the first call that emits no logs. Two small
fixed-width fields do not pay for that hazard, so they are always present and a
decoder never has to branch on a count it has already parsed to know where the
next section starts.
revm-wasm@0.3.1
Patch Changes
-
d302e99: Fix intrinsic gas computed at wrong spec (GitHub issue #4)
CallExecutor::newsetCfgEnv::specdirectly, which leftCfgEnv::gas_params
at theContext::mainnet()default (SpecId::OSAKA). The intrinsic-gas table
therefore always carried EIP-3860's initcode word cost (introduced in Shanghai)
and EIP-7623's calldata floor (introduced in Prague), regardless of the spec
the caller requested.This caused two observable bugs on pre-Shanghai and pre-Prague forks:
Outcome.gasUsedreported the EIP-7623 calldata floor on specs that predate
Prague.- EIP-3860's initcode word cost was charged on specs that predate Shanghai
(BERLIN,LONDON,MERGE).
Opcode gating was unaffected because the instruction table is built from the
correct spec; only the intrinsic-gas path used the stalegas_params.The fix replaces
c.spec = specwithc.set_spec_and_mainnet_gas_params(spec),
which rebuilds the gas table for the requested spec (and enables EIP-8037/EIP-2780
for AMSTERDAM and later).
revm-wasm@0.3.0
Minor Changes
-
778d788: Expose
disableEip3607onExecuteOptions, so acall()can be made from a contract address.EIP-3607 rejects a transaction whose sender has deployed code. It is a transaction-validity rule, not an execution rule — it stops a transaction being sent from an address that carries code — but revm applies it on the
call()path too, so aneth_callfrom a contract address is rejected withTransaction(RejectCallerWithCode). Simulating a call from a contract address is ordinary practice: smart-account and ERC-4337 flows, multicall aggregators, and any UI that previews what one contract sees when called by another.@ethereumjs/evm'srunCall(which is whateth_callgoes through) does not enforce EIP-3607 at all, so the same read succeeds on ethereumjs and fails on revm, and a user gets a different answer purely from which engine they opted into.New option, defaulting to
false, so nothing changes for a caller that does not ask:disableEip3607(revm'sdisable_eip3607) skips EIP-3607, so a read from a contract address succeeds
evm.call({ from: contractAddress, to, data, gasLimit, disableEip3607: true, });
Gas does not move: EIP-3607 is a pre-execution check, so disabling it changes whether the call runs at all, never what it costs once it does.
It may not be combined with committing, for the same reason as the other simulation switches: a committed transaction from a contract address is one the chain would reject, which breaks the cross-engine equivalence a consumer's gate exists to protect.
transact({commit: true, disableEip3607: true})throws. Usecall(), ortransact({commit: false}).The legacy
RELAX_VALIDATIONbit (bit 2) is not extended to include EIP-3607: its meaning is fixed by ADR 0004 as exactly the union of the three switches it always described (disableBaseFee,disableBalanceCheck,disableBlockGasLimit), and reusing it for a new capability would silently change every caller that sets it.disableEip3607is opted into individually through its own flag bit (bit 7).The shipped
revm.wasmnow enables revm'soptional_eip3607feature (added to therelaxed-validationcargo feature). The switch defaults tofalseand revm's accessor returns the same value as when the field is absent, so no existing behaviour or gas changes. Seedocs/adr/0006-simulation-switches.md.
revm-wasm@0.2.0
Minor Changes
-
b5d2e0e: Expose revm's transaction-validation switches on
ExecuteOptions, so a read can keep its real block environment.call()validated every execution as if it were going to be mined, and two of those checks are unsatisfiable for a node servingeth_call: the gas price against the block's base fee (GasPriceLessThanBasefee), and the caller's balance againstgasLimit * gasPrice(LackOfFundForMaxFee).eth_calldefaultsfromto the zero address and callers routinely simulate from addresses holding no ether, against blocks that carry a non-zero base fee. The only way through was to passbaseFeePerGas: 0non every read, which makes a contract that readsblock.basefeein a view function see a number the chain never had.New options, all defaulting to
false, so nothing changes for a caller that does not ask:disableBaseFee(revm'sdisable_base_fee) skipsgasPrice >= block base feedisableBalanceCheck(revm'sdisable_balance_check) skipsbalance >= gasLimit * gasPrice + valuedisableBlockGasLimit(revm'sdisable_block_gas_limit) skipsgasLimit <= block gas limit
evm.call({ from, to, data, gasLimit, block: {...realBlock}, // the real base fee, not a zeroed one disableBaseFee: true, disableBalanceCheck: true, });
Gas does not move: the charge is fee-independent, so a consumer replacing the zeroed-base-fee workaround with the real base fee plus
disableBaseFeesees identicalgasUsed,totalGasSpentandgasRefunded. What changes is thatBASEFEEnow reports the truth.They cannot be combined with committing.
transact({commit: true, disableBalanceCheck: true})throws. revm raises the caller's balance to cover the value being sent, so committing would write funds that never existed into your store, silently. Usecall(), ortransact({commit: false})to simulate a transaction from an account that cannot pay for it.block.prevRandaois now settable, soPREVRANDAOreads the block's real value instead of always zero.Fixed: the block environment no longer leaks between calls. One EVM instance serves many calls, and
excessBlobGaswas only written when the caller supplied it, so a call that set it left the blob gas price in place for every later call that did not. Every block field is now assigned on every call; an absent value means its zero rather than "whatever ran last". AbsentexcessBlobGasstill yields the 1 wei minimum blob gas price, so nothing moves for a caller that always passed it.The shipped
revm.wasmnow enables revm'soptional_balance_check,optional_no_base_feeandoptional_block_gas_limitfeatures. Every switch defaults to false and revm's accessors return the same values as when the fields are absent, so no existing behaviour or gas changes; the full recorded corpus is byte-identical. Cost: +365 bytes raw, nothing measurable gzipped (measurements/validation-switches-size.json). Seedocs/adr/0006-simulation-switches.md.
revm-wasm@0.1.0
Minor Changes
-
82f3e91: Initial release: revm compiled to WebAssembly with a typed JavaScript API.
An UNOFFICIAL binding for revm (bluealloy, MIT), pinned at revision
640eafa91beae73bafb7776845d53133f603048f(crate version 42.0.1). Deliberately not a general-purpose binding: the build configuration is fixed, the host interface has one shape, and v1 ships no custom precompiles and no inspector.What is in it
- A prebuilt
revm.wasm(420 KB gzipped) shipped inside the tarball. No Rust toolchain is ever needed in a consuming project or its CI. createRevm/createRevmSync, taking wasm as bytes, a URL, aResponseor a compiledWebAssembly.Module.call(read-only),transact(committing),create(contract creation) andrecoverSigner. Every entry point takes an options object, never positional arguments.StateStore: a synchronous state interface that is an adapter over the consumer's own storage.MemoryStoreis provided for tests, not as the place state lives. The raw pointer-levelHostFunctionsinterface is exported for consumers who want zero marshalling.- Typed outcomes with no byte offsets, flag bits or packed account layouts: named booleans (
created,deleted,selfdestructed),bigintbalances, and alogsBloomthat is always 256 bytes. revmVersion,revmRevision,outcomeFormatVersionandabiVersionreadable at runtime, so a bug report can state exactly what was running.
Build configuration, which is fixed
Every precompile, at
opt-level = 3. Neither half is negotiable: omitting a precompile stops its address being pre-warmed and costs +2,500 gas per touch, andopt-level = "z"halves the artifact while costing 2.4x to 6x on keccak. Seedocs/adr/0001-all-precompiles-opt-level-3.md.Notable design decisions
- No wasm-bindgen. The module exposes a raw C ABI and host functions are bound per
WebAssembly.instantiate, so two instances in one page cannot see each other's linear memory. The glue-based approach made that failure silent and plausible-looking. - Storage is indexed per account, so
clearStorageis O(one account) rather than O(total state).SELFDESTRUCTand every contract creation hit it. - Map keys pack two bytes per UTF-16 code unit instead of two hex characters per byte: 3.5x faster key handling on this machine.
Testing
Nine fixtures, 63 calls, compared byte for byte against outcomes recorded from native revm, run in Node, Chromium (V8) and WebKit (JavaScriptCore). The suite needs no Rust toolchain.
- A prebuilt