Skip to content

bug(evm): EIP-7702 delegation to an Arc precompile address executes the genesis placeholder code instead of empty code #498

Open

Description

@forumevi

Summary

EIP-7702 specifies: "In case a delegation designator points to a precompile address, retrieved code is considered empty and CALL, CALLCODE, STATICCALL, DELEGATECALL instructions targeting this account will execute empty code, i.e. succeed with no execution given enough gas."

On Ethereum this falls out naturally because precompile accounts have no code. On Arc, every account in 0x1800000000000000000000000000000000000000–0x18000000000000000000000000000000000000ff is allocated in genesis with placeholder code and nonce 1:

  • assets/mainnet/genesis.json, assets/localdev/genesis.json: code: 0xef
  • assets/testnet/genesis.json, assets/devnet/genesis.json: code: 0x01

When an EOA delegates to one of these addresses (e.g. 0x1800…0001, NativeCoinControl), revm resolves the delegate's bytecode with no precompile special case, and PrecompilesMap::run keys on bytecode_address (the EOA), so the precompile is not dispatched either. Every call into the delegated EOA therefore runs the placeholder byte:

  • mainnet/localdev: 0xef → InvalidFEOpcode, all forwarded gas consumed
  • testnet/devnet: 0x01 (ADD on empty stack) → StackUnderflow, all forwarded gas consumed

Expected per EIP-7702: the call succeeds as empty code (STOP).

Affected code (main, 6e76402)

  • Genesis allocs listed above (placeholder code on 0x1800..00–0xff, pragueTime: 0 on all networks).
  • revm-handler-18.1.0/src/frame.rs make_call_frame: runs known_bytecode (the delegate's code) and only short-circuits when it is empty.
  • revm-context-16.0.1/src/journal/inner.rs:741-775 load_account_delegated: loads the delegate account with no precompile check.
  • alloy-evm-0.34.0/src/precompiles.rs:542: precompile lookup by inputs.bytecode_address.

Impact

Low / spec deviation. Only the account that signed the authorization is affected: incoming calls (including plain value-0 calls and token callbacks) to that EOA halt and burn gas until it re-delegates or clears its delegation. Execution is deterministic across nodes, so there is no consensus divergence. Tooling and wallets that rely on the EIP-7702 precompile rule will see different behavior on Arc than on Ethereum.

Reproduction

Unit tests added to the tests module of crates/evm/src/evm.rs (use the existing create_db / create_arc_evm helpers). The first is a control with a standard precompile (identity, 0x04); the second uses NativeCoinControl with the mainnet placeholder 0xef.

fn eip7702_delegation_call(delegate: Address, delegate_code: Option<revm::bytecode::Bytecode>) -> ExecutionResult {
    use revm::bytecode::Bytecode;
    use revm_primitives::TxKind;

    let sender = Address::repeat_byte(0x0a);
    let delegator = Address::repeat_byte(0x0b);
    let mut db = create_db(&[(sender, 1_000_000)]);
    let designator = Bytecode::new_eip7702(delegate);
    db.insert_account_info(delegator, AccountInfo {
        balance: U256::ZERO, nonce: 1,
        code_hash: designator.hash_slow(), code: Some(designator), account_id: None,
    });
    if let Some(code) = delegate_code {
        db.insert_account_info(delegate, AccountInfo {
            balance: U256::ZERO, nonce: 1,
            code_hash: code.hash_slow(), code: Some(code), account_id: None,
        });
    }
    let chain_spec = LOCAL_DEV.clone();
    let mut evm = create_arc_evm(chain_spec.clone(), db);
    let tx = TxEnv {
        caller: sender, kind: TxKind::Call(delegator), value: U256::ZERO, nonce: 0,
        gas_limit: 100_000, gas_price: 0, chain_id: Some(chain_spec.chain_id()),
        ..Default::default()
    };
    ExecuteEvm::transact(&mut evm, tx).expect("tx should execute").result
}

#[test]
fn test_eip7702_delegation_to_standard_precompile_executes_empty_code() {
    let result = eip7702_delegation_call(Address::with_last_byte(0x04), None);
    assert!(result.is_success(), "control: expected success, got {result:?}");
}

#[test]
fn test_eip7702_delegation_to_arc_precompile_executes_empty_code() {
    let placeholder = revm::bytecode::Bytecode::new_raw(Bytes::from_static(&[0xef]));
    let result = eip7702_delegation_call(NATIVE_COIN_CONTROL_ADDRESS, Some(placeholder));
    assert!(result.is_success(),
        "call into an account delegated to an Arc precompile must succeed with empty code, got {result:?}");
}

Run: cargo test -p arc-evm --lib test_eip7702_delegation_to_

Expected: both pass. Actual on main (from code reading): the control passes; the Arc case halts with InvalidFEOpcode and consumes all gas.

Suggested fix

When resolving the code of an EIP-7702 delegated account (in Arc's frame/handler layer, before make_call_frame runs known_bytecode), treat the delegated code as empty if the delegate address is in Arc's precompile set (static 0x1800.. range and any dynamic precompiles). Rejecting such authorizations in the txpool alone is not enough, since they can be included by any proposer and the rule is an execution rule. A hardfork gate is likely needed because it changes execution results.

Environment

  • arc-node main @ 6e76402
  • revm-handler 18.1.0, revm-context 16.0.1, alloy-evm 0.34.0

Activity

  1. kutluhaneth46 commented on Oct 31, 2026

    @kutluhaneth46
    Contributor

    Hi — this looks still unassigned. I'd like to take it if the team is open to an external fix.

    Plan: reproduce with the two test_eip7702_delegation_to_* cases sketched in the issue (arc-evm lib tests), then short-circuit EIP-7702 delegation targets that are Arc precompiles so the call behaves as empty code per the EIP (instead of executing genesis placeholder 0xef / 0x01). Happy to wait for assignment or a maintainer ACK before opening a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions