EIP.tools

EIP.tools

⚠️ DraftStandards Track: Core

EIP-7990: Opcode for arbitrary bytecode execution

Add RUNCODE opcode to execute arbitrary bytecode from memory within the same execution context

Authors
Created2025-07-19
Discussion Linkhttps://ethereum-magicians.org/t/eip-7990-runcode-opcode-execute-arbitrary-bytecode-from-memory-within-the-same-execution-context/24850

Markdown

https://raw.githubusercontent.com/ethereum/EIPs/re...
Pull Request#10036PR open

EIP-GPT summary

Contents
AbstractMotivationSpecificationRUNCODE OpcodeBehaviorOpcode RestrictionsGas MeteringMemory RequirementsExecution FlowRationaleDesign DecisionsGas Optimization BenefitsAlternative Approaches ConsideredBackwards CompatibilityTest CasesBasic Execution with Return ValueCold Address Gas Savings ComparisonGas Limit Enforcement with 63/64 RuleReference ImplementationSecurity ConsiderationsPotential RisksMitigationsSecurity RecommendationsComparison to Existing RisksCopyright

Abstract

This EIP proposes a new opcode RUNCODE that allows execution of arbitrary bytecode from memory within the same execution context as the calling contract, but with its own isolated stack. This eliminates the need for delegate calls to execute dynamic code, providing significant gas savings by avoiding cold address access costs while maintaining the same storage, balance, and address context.

Motivation

Current Ethereum contracts requiring dynamic code execution must use DELEGATECALL to external contracts containing the desired bytecode. This approach has several limitations:

  1. Cold address access overhead: DELEGATECALL incurs a 2600 gas cold address access penalty when the target contract hasn't been accessed in the current transaction, plus the 100 gas base call cost
  2. Address dependency: Requires managing and accessing external contract addresses, creating additional state dependencies
  3. Deployment requirements: Dynamic code must be pre-deployed as contracts, consuming storage and deployment gas (~32,000 gas + ~200 gas per byte)
  4. Address space pollution: Each piece of dynamic code requires a unique contract address, leading to blockchain state bloat
  5. Architectural complexity: Managing multiple deployed contracts for dynamic execution adds significant complexity to contract systems

Common patterns that would benefit include:

  • Factory contracts calling multiple dynamic computation libraries
  • Upgradeable proxy contracts with frequently changing implementations
  • Meta-programming patterns where code is generated and executed at runtime
  • Mathematical computation libraries that generate optimized bytecode
  • One-time execution of complex calculations without permanent on-chain storage

The RUNCODE opcode addresses these limitations by allowing direct execution of bytecode from memory within the current execution context, eliminating address access costs and deployment requirements while providing the same functionality as DELEGATECALL.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

RUNCODE Opcode

  • Opcode: 0xF6
  • Name: RUNCODE
  • Gas Cost: 100 base gas (equivalent to DELEGATECALL base cost without address access)
  • Stack Input: [gas, codeOffset, codeLength, argsOffset, argsLength, retOffset, retLength]
  • Stack Output: [success]

Behavior

  1. Input Parameters: Takes seven stack parameters (identical to DELEGATECALL interface):

    • gas: Amount of gas to provide to the dynamic execution
    • codeOffset: Memory offset where bytecode starts
    • codeLength: Length of bytecode in bytes
    • argsOffset: Memory offset of input data to pass to dynamic code
    • argsLength: Length of input data in bytes
    • retOffset: Memory offset to store return data
    • retLength: Maximum length of return data to copy back
  2. Execution Context:

    • Creates a new isolated execution stack for dynamic code
    • Copies input data from [argsOffset, argsOffset+argsLength) to the new execution's calldata
    • Executes bytecode from [codeOffset, codeOffset+codeLength) with the provided gas limit
    • Maintains identical context to calling contract: same msg.sender, msg.value, storage, balance, and address
    • All context variables (CALLER, ORIGIN, GASPRICE, CHAINID, etc.) remain unchanged
  3. Return Mechanism:

    • Dynamic code terminates with RETURN (success) or REVERT (failure)
    • Return data is copied to caller's memory at [retOffset, retOffset+min(actualReturnLength, retLength))
    • Returns 1 on successful execution, 0 on failure (revert, out-of-gas, or invalid bytecode)
    • Unused gas is returned to the caller
    • Return data length is available via existing RETURNDATASIZE opcode
  4. Context Preservation:

    • Storage operations (SLOAD/SSTORE) access the calling contract's storage state
    • Balance operations (BALANCE, SELFBALANCE) reference the calling contract's balance
    • Address operations (ADDRESS) return the calling contract's address
    • All environmental variables remain identical to the calling context

Opcode Restrictions

All standard EVM opcodes are permitted within dynamic code execution without restriction, including:

  • State modification: SSTORE, SELFDESTRUCT
  • Contract creation: CREATE, CREATE2
  • External calls: CALL, DELEGATECALL, STATICCALL
  • Control flow: JUMP, JUMPI, RETURN, REVERT
  • Dynamic execution: RUNCODE
  • All arithmetic, logical, and memory operations

This maintains full consistency with DELEGATECALL behavior, which can also call itself recursively. Recursive RUNCODE execution is permitted and follows the same gas limiting and stack depth restrictions as other recursive operations in the EVM.

Gas Metering

  • Base cost: 100 gas (equivalent to DELEGATECALL base cost)
  • Cold address savings: Unlike DELEGATECALL, no additional 2600 gas cold address access cost is incurred since no external address is accessed
  • Total potential savings: Up to 2600 gas per execution when compared to DELEGATECALL to cold addresses
  • Dynamic execution cost: Each opcode within the dynamic bytecode consumes gas from the provided gas allocation
  • Gas limiting: If provided gas is insufficient for complete execution, the operation fails and returns 0
  • Gas refund: Unused gas from the allocation is returned to the calling context following the 63/64 rule (maximum 63/64 of remaining gas can be allocated)
  • Out-of-gas handling: Standard EVM out-of-gas behavior applies; execution halts and returns failure

Memory Requirements

  • All memory ranges specified in parameters MUST be accessible within current memory bounds
  • Memory expansion costs apply normally if accessing memory beyond current bounds during parameter reading
  • Memory expansion costs within dynamic code execution are charged against the provided gas allocation
  • Input data copying incurs standard memory copy costs
  • Return data copying follows standard EVM memory expansion rules

Execution Flow

  1. Parameter validation: Pop seven parameters from stack and validate memory ranges
  2. Gas calculation: Apply 63/64 rule to determine maximum allocatable gas
  3. Context preparation: Create isolated execution stack and copy input data to new calldata
  4. Dynamic execution: Execute bytecode with gas limit, maintaining calling contract's context
  5. Result handling: On completion, copy return data and push success status to caller's stack
  6. Gas accounting: Return unused gas to calling context

Recursive RUNCODE calls are subject to the same call depth limitations as other EVM operations (1024 levels maximum).

Rationale

Design Decisions

  1. DELEGATECALL-compatible interface: Using identical parameter structure ensures familiar semantics for developers and enables drop-in replacement for many DELEGATECALL patterns.

  2. Isolated execution stack: Prevents stack contamination while enabling proper argument passing and return value handling, maintaining clean separation between caller and dynamic code.

  3. Explicit gas control: Provides fine-grained control over execution costs and prevents dynamic code from consuming all available gas unexpectedly.

  4. Memory-based I/O: Using memory for arguments and return values follows established EVM patterns and provides maximum flexibility for data exchange.

  5. Context preservation: Maintaining identical execution context to DELEGATECALL ensures compatibility with existing patterns while providing gas savings.

  6. Full opcode compatibility: Allowing all opcodes including recursive RUNCODE maintains consistency with DELEGATECALL behavior and provides maximum flexibility for dynamic programming patterns.

Gas Optimization Benefits

The primary advantage over DELEGATECALL is eliminating cold address access costs:

  • DELEGATECALL to cold address: 2600 (cold access) + 100 (base cost) = 2700 gas minimum
  • DELEGATECALL to warm address: 100 (warm access) + 100 (base cost) = 200 gas minimum
  • RUNCODE execution: 100 gas base cost only
  • Net savings: 2600 gas per cold address access (96% reduction in base costs for cold calls)
  • Net savings: 100 gas per warm address access (50% reduction in base costs for warm calls)

Additional savings come from eliminating contract deployment for temporary or generated code:

  • RUNCODE: Only execution costs, no deployment required

Alternative Approaches Considered

  • Simple stack-based execution: Would pollute caller's stack and complicate parameter passing
  • Shared execution context: Could create security vulnerabilities and unpredictable behavior
  • No gas limiting: Would enable griefing attacks and unpredictable gas consumption
  • Restricted opcode subset: Would limit functionality and create inconsistency with DELEGATECALL

Backwards Compatibility

This EIP introduces a new opcode (0xF6) and does not modify any existing EVM behavior. Existing contracts continue to function unchanged. No hard fork compatibility issues are expected.

Test Cases

Basic Execution with Return Value

// Bytecode: PUSH1 0x42 PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x00 RETURN
// Hex: 0x604260005260206000F3

// Store bytecode at memory offset 0x00
PUSH10 0x604260005260206000F3  // 10 bytes of code
PUSH1 0x00
MSTORE                         // Stores padded in 32 bytes

// Execute: gas=1000, codeOffset=0x00, codeLength=10, argsOffset=0x40, argsLength=0, retOffset=0x60, retLength=32
PUSH1 0x20    // retLength
PUSH1 0x60    // retOffset  
PUSH1 0x00    // argsLength
PUSH1 0x40    // argsOffset
PUSH1 0x0A    // codeLength
PUSH1 0x00    // codeOffset
PUSH2 0x03E8  // gas (1000)
RUNCODE
// Returns 1 (success), 0x42 stored at memory offset 0x60

Cold Address Gas Savings Comparison

// DELEGATECALL to cold address costs 2700 gas minimum
PUSH1 0x00    // retSize
PUSH1 0x00    // retOffset
PUSH1 0x00    // argSize  
PUSH1 0x00    // argOffset
PUSH20 0x1234567890123456789012345678901234567890  // cold address
PUSH2 0x2710  // gas (10000)
DELEGATECALL  // Costs: 2600 (cold) + 100 (base) + execution = 2700+ gas


// Equivalent RUNCODE execution costs only 100 gas base
// Suppose equivalent bytecode = 0x60006000F3 (RETURN 0)
PUSH4 0x60006000F3   // 4 bytes code
PUSH1 0x00
MSTORE               // Stores at memory[0x00]

// Execute from memory
PUSH1 0x00    // retLength
PUSH1 0x80    // retOffset  
PUSH1 0x00    // argsLength
PUSH1 0x40    // argsOffset
PUSH1 0x04    // codeLength
PUSH1 0x00    // codeOffset
PUSH2 0x2710  // gas (10000)
RUNCODE       // Costs: 100 (base) + execution = 2600 gas savings

Gas Limit Enforcement with 63/64 Rule

// Store bytecode that requires exactly 500 gas to execute
// Assume it's 0x60016000F3 (simple RETURN)
PUSH4 0x60016000F3
PUSH1 0x00
MSTORE

// With 10000 gas available, max allocatable = 10000 * 63/64 = 9843 gas
PUSH1 0x00    // retLength
PUSH1 0x80    // retOffset  
PUSH1 0x00    // argsLength
PUSH1 0x40    // argsOffset
PUSH1 0x04    // codeLength
PUSH1 0x00    // codeOffset
PUSH2 0x2710  // gas (10000)
RUNCODE
// Can allocate up to 9843 gas, more than sufficient for 500 gas execution
// Returns 1 (success), unused gas refunded following EVM rules

Reference Implementation

A reference implementation requires the following EVM modifications:

  1. Opcode registration: Add 0xF6 opcode with RUNCODE handler in the interpreter
  2. Parameter parsing: Extract seven parameters from execution stack with proper validation
  3. Gas calculation: Apply 63/64 rule for maximum gas allocation
  4. Context isolation: Create separate execution stack while preserving contract context
  5. Calldata simulation: Copy input arguments to new execution context's calldata
  6. Gas management: Implement gas limiting, accounting, and unused gas refunding
  7. Result handling: Copy return data and provide success/failure status
  8. Call depth tracking: Ensure recursive calls respect the 1024 level call stack limit

No bytecode validation is required since all opcodes are permitted, maintaining consistency with DELEGATECALL which also allows recursive calls to itself.

Security Considerations

Potential Risks

  1. Arbitrary code execution: Dynamic bytecode execution introduces eval()-like capabilities, enabling arbitrary contract behavior modification at runtime

  2. Reentrancy amplification: Dynamic code can create new and unpredictable reentrancy attack vectors, especially when combined with external calls

  3. Gas griefing: Malicious actors could craft bytecode designed to consume maximum gas while providing minimal utility

  4. State manipulation: Dynamic code has full access to contract storage, enabling potentially destructive state modifications

  5. Audit complexity: Contracts using RUNCODE become significantly harder to audit and formally verify due to runtime code generation

Mitigations

  1. Gas limiting: Standard EVM gas mechanics apply to dynamic execution, preventing runaway computation and providing cost predictability

  2. 63/64 rule: Standard EVM gas forwarding rules apply, preventing the calling context from being completely drained of gas

  3. Call depth limits: EVM's built-in 1024-level call depth limit naturally prevents excessive recursion in RUNCODE calls

  4. Context isolation: Execution within the calling contract's context limits the blast radius of malicious operations to the calling contract's state

Security Recommendations

  1. Input validation: Contracts using RUNCODE should implement strict validation of bytecode before execution, potentially using allowlists for permitted instruction patterns

  2. Access control: Implement appropriate authorization mechanisms around RUNCODE usage to prevent unauthorized dynamic code execution

  3. Audit practices: Dynamic code generation and execution logic requires thorough security auditing with particular attention to input validation and state modification patterns

  4. Gas budgeting: Use conservative gas limits for dynamic execution to prevent excessive resource consumption and ensure predictable costs

  5. Recursion awareness: When allowing recursive patterns, ensure proper gas budgeting to prevent call depth or out-of-gas issues

  6. State protection: Consider implementing state snapshots or rollback mechanisms for critical contract state when using dynamic code execution

  7. Monitoring: Implement comprehensive logging and monitoring for RUNCODE usage to detect potential misuse or attacks

Comparison to Existing Risks

The security model is similar to DELEGATECALL in terms of context preservation and state access, but with additional considerations around dynamic code generation. The primary new risk is the ability to execute arbitrary bytecode patterns, which requires careful input validation and access control.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth