EVM#5410
Conversation
Add shared EVM-native types, global-state keys and stored values, chainspec configuration, and a new casper-executor-evm crate backed by revm. The executor runs against a caller-provided TrackingCopy so callers can decide whether to commit effects or discard a forked view execution. This introduces typed EVM account, bytecode, and storage records so contract state can be queried and pruned by address prefix. EVM balances are backed by deterministic Casper main purses, with balance reads and writes reconciled through existing purse balance storage. Signed Ethereum RLP decoding and sender recovery are added to casper-types for legacy, EIP-2930, and EIP-1559 transactions. Blob transactions and EIP-7702 set-code transactions return explicit unsupported errors in this first pass. The node and storage layers learn the new transaction/hash/initiator shapes without routing EVM transactions through existing Casper transaction execution yet. Chainspecs now include disabled-by-default EVM configuration with CSP namespace chain IDs. Solidity fixtures, Makefile targets, and integration tests cover deployment, calls, ERC20/ERC721 behavior, storage deletion, selfdestruct semantics, and native EVM purse-balance reconciliation.
Require signed EVM transactions to carry the configured chain ID so legacy pre-EIP-155 transactions cannot bypass the Casper EVM replay domain. Reject non-empty Ethereum access lists until the executor maps them into revm transaction environments. Add explicit validation modes for unsigned call requests so simulation calls remain available while commit-capable calls can opt into revm balance, nonce, chain-id, base-fee, and gas-limit checks. Add a block hash provider abstraction for the EVM BLOCKHASH opcode, including an LMDB-backed provider for future node wiring and tests for the 256-block lookup window. Complete selfdestruct cleanup by pruning the deterministic main purse balance alongside the EVM account and storage.# Please enter the commit message for your changes. Lines starting
Bump the pinned stable Rust toolchain from 1.85.1 to 1.91.0 so the workspace can use dependencies that require the newer compiler. Keep the existing Makefile lint gate on -D warnings while allowing lint categories that were introduced or tightened by newer clippy versions and would otherwise force unrelated API churn. Apply small mechanical updates and targeted dead-code annotations needed for the 1.91.0 lint run.
Allow native transfer targets to be encoded as 20-byte EVM addresses when EVM support is enabled. Resolve those targets to existing EVM account purses or create deterministic EVM account records on first funding, while preserving the native transfer record schema. Stop seeding EVM accounts at genesis and require explicit funding through native transfers. Reject EVM address transfer targets when EVM is disabled, add the binary-port error code, and cover the genesis and transfer behavior with tests. Document the explicit EVM funding flow and add CL typing/schema support for EVM addresses.
EVM addresses and Casper account hashes come from different preimages, so using the same signing key currently gives users two separate identities. This change adds a small identity record under `EvmAddr::Account` so an EVM address can point either at an existing Casper account or at an EVM-native purse. The old EVM account blob is split into separate records for identity, nonce, code hash, bytecode, and storage. Runtime and transaction validation now resolve the EVM sender before balance and nonce checks, while the executor only reads and writes the new layout. When an EVM transaction is signed by a key that already has a Casper account, the runtime links the EVM sender address to that account. When no Casper account exists yet, it creates one using the deterministic EVM purse as the main purse. EVM-native accounts and contract-created accounts stay purse-backed and are not forced into Casper account identities. Native transfers to EVM addresses now follow the same identity model: linked accounts credit the Casper main purse, EVM-native identities credit their purse, and missing targets create the deterministic purse-backed identity first. This also removes the unreleased `EvmValue::Account` and `EvmValue::ByteCode` storage forms, storing bytecode as `StoredValue::ByteCode` instead.
Introduce type 0x04 EVM transaction decoding, serialization, signing, verification, config checks, and executor translation. Store signed authorization-list items as transaction data while keeping Casper approvals limited to the outer Ethereum signature. Pass EIP-7702 authorization lists through to revm so Prague execution handles authority recovery, delegation writes, invalid-entry skipping, nonce updates, and delegation clearing. Keep existing Casper policies for blob transactions, non-empty access lists, and non-zero priority fees. Normalize missing dynamic priority fees to zero in the executor to match validation. Update sidecar dependency patches, receipt projection, raw transaction tests, and JSON schema snapshots for type 0x04 transactions. Document EIP-7702 support and devnet verification steps, and add coverage for type-4 decoding, round trips, config compliance, executor semantics, and sidecar raw transaction handling.
Remove the binary-port EVM call simulation command and route sidecar read-only EVM calls through TrySpeculativeExec. Add an unsigned EVM call marker transaction that carries chain ID and gas price so normal EVM config compliance still applies before execution. Return EVM speculative execution data through a dedicated binary-port result type, and keep unsigned call marker transactions rejected by transaction acceptance so they cannot be submitted as normal network transactions.
| } | ||
|
|
||
| #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] | ||
| pub struct EvmSpeculativeExecutionResult { |
There was a problem hiding this comment.
Do we really need another struct for that? I think we could add "output: Bytes" to the existing struct SpeculativeExecutionResult, right?
There was a problem hiding this comment.
Fair point for output bytes, although for VM1, it's not raw bytes, it's CLValue. Fitting raw output bytes from EVM into CLValue feels weird. There's also evm_receipt in there, which is very EVM-specific.
| let account_hash = initiator_addr.account_hash(); | ||
| let account_hash = initiator_addr | ||
| .account_hash() | ||
| .expect("Wasm v1 initiator must be a Casper account"); |
There was a problem hiding this comment.
But if this is an evm account backed by a casper account this still should be doable, right?
| /// An account hash. | ||
| AccountHash(AccountHash), | ||
| /// An EVM address. | ||
| EvmAddress(evm::Address), |
There was a problem hiding this comment.
Why do we need a separate transfer target for this? I thought that this will ultimately be resolved to either an account hash or a purse uref?
There was a problem hiding this comment.
The idea here is that a user with a Casper identity (aka Key::Account) can seed EVM addresses via native transfers. Think of exchanges, wallets, etc, being able to send funds to 20-byte addresses using their funds from existing hot wallets.
| /// An account hash. | ||
| AccountHash(AccountHash), | ||
| /// An EVM address. | ||
| EvmAddress(evm::Address), |
There was a problem hiding this comment.
the "random" function doesn't include the new variant
| // Revm still computes Ethereum fee transfers internally before we remove | ||
| // them for Casper-owned fee accounting. For node-accepted EIP-1559 | ||
| // transactions the priority fee is zero by policy, but this stays generic | ||
| // for executor callers and pre-London specs. |
There was a problem hiding this comment.
Should we be concerned with logic that supports transactions that were valid a long time ago? We won't have pre-Prague evm txes in our node, right?
| match runtime | ||
| .transfer( | ||
| Some(initiator_addr.account_hash()), | ||
| initiator_addr.account_hash(), |
There was a problem hiding this comment.
I don't think None meant the same thing as it does now
|
|
||
| /// Returns the rewards. | ||
| pub fn rewards(&self) -> Rewards { | ||
| pub fn rewards(&self) -> Rewards<'_> { |
There was a problem hiding this comment.
This is a rust-toolchain bump requirement?
There was a problem hiding this comment.
does this have to be part of the public api? I see it only being used as a pub use in evm.rs
| #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] | ||
| #[cfg_attr(feature = "datasize", derive(DataSize))] | ||
| #[cfg_attr(feature = "json-schema", derive(JsonSchema))] | ||
| pub struct Transaction { |
There was a problem hiding this comment.
Not sure if we want to call it a "transaction" since it has such a load-bearing meaning in the node
| /// When support for a future bytecode-affecting EVM spec such as Osaka is | ||
| /// officially added, introduce a new `Evm<Spec>` variant instead of | ||
| /// changing the meaning of this one. | ||
| EvmPrague = 3, |
There was a problem hiding this comment.
So inherently if someone gives us a specid that is pre-prague we should be rejecting it, right?
Summary
This PR adds a proof-of-concept EVM execution path to
casper-node. It proves that Casper can accept, validate, execute, account for, and persist Ethereum-shaped transactions while keeping the execution and storage model rooted in Casper: global state, transaction flow, account/purse accounting, fee/refund policy.The companion
casper-sidecarchanges in casper-network/casper-sidecar#468 provide the Ethereum JSON-RPC surface for this PoC. Sidecar currently implementseth_chainId,eth_blockNumber,eth_getBlockByNumber,eth_getTransactionCount,eth_sendRawTransaction,eth_getTransactionReceipt,eth_getLogs,eth_newFilter,eth_getFilterChanges,eth_getFilterLogs,eth_uninstallFilter,eth_call, and websocket log subscriptions. It uses node binary-port APIs to submit signed EVM transactions, run speculative EVM calls, read blocks and transaction execution info, and project Casper EVM receipts/logs into Ethereum-compatible JSON-RPC responses.What Changed
On the node side, this branch introduces:
Transaction::EvmandTransactionHash::Evm.U256JSON quantities.casper-executor-evmcrate backed byrevm.ExecutionResult::Evm, carrying EVM receipt data plus Casper accounting fields.eth_call.What Works
The current PoC supports signed Ethereum RLP ingestion through sidecar, conversion into
Transaction::Evm, node-side validation, execution throughrevm, persistence of EVM state, and Ethereum-compatible receipt projection.Tested flows include contract deployment, contract calls, native EVM value transfer, event logs in receipts, simple ERC20/ERC721-style behavior, speculative
eth_call, native Casper funding of EVM addresses, and EIP-7702 delegated-code execution using Foundry.The PoC supports the Ethereum transaction envelopes needed for the current tooling flow:
max_priority_fee_per_gas == 0.Prague/Pectra Compatibility
The executor is configured with a Prague-capable EVM spec, and this branch implements the main PoC execution path needed for EIP-7702. In that sense, the exercised EVM path is Prague-compatible.
It is not yet a complete Prague/Electra network implementation. The canonical Prague/Electra meta EIP is EIP-7600. Remaining Prague/Pectra work includes:
requests_hash.Blob transactions are still rejected. Full blob support requires EIP-4844 sidecars, KZG data, blob gas accounting, and the Prague blob parameter updates from EIP-7691. Client configuration should also eventually account for EIP-7840.
Current Limitations
EVM is disabled by default in chainspecs and must be explicitly enabled.
The sidecar RPC surface is enough for the PoC Foundry flow, transaction submission, receipts, calls, logs, filters, and log subscriptions. Some Ethereum RPC methods are still future work, including
eth_estimateGas,eth_getBalance,eth_getCode, andeth_getTransactionByHash.eth_getBlockByNumbercurrently returns transaction hashes only; full transaction objects are explicitly rejected for now. Block and receipt projection still needs to mature around complete Ethereum roots, bloom handling, gas-used accounting, and historical query performance.Access lists are decoded but non-empty lists are rejected. Priority fees are also rejected because Casper does not currently prioritize transactions using Ethereum fee-market tips.
Future Work
Next steps are to move this from PoC towards a spec-complete feature:
Not every Ethereum feature maps cleanly onto Casper’s existing execution, storage, accounting, and consensus model. For those cases, the plan is to make a best-effort compatibility layer, keep the Casper-side semantics explicit, and document where behavior intentionally differs from Ethereum mainnet.
Validation
This branch adds unit and integration coverage for EVM transaction decoding, approval handling, executor behavior, account/state storage, receipts, native transfers to EVM addresses, nonce validation, binary-port speculative EVM calls, Casper fee/refund handling, and EIP-7702 delegation behavior.
The manual Foundry validation flow is documented in
EVM.md.