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:
- Cold address access overhead:
DELEGATECALLincurs 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 - Address dependency: Requires managing and accessing external contract addresses, creating additional state dependencies
- Deployment requirements: Dynamic code must be pre-deployed as contracts, consuming storage and deployment gas (~32,000 gas + ~200 gas per byte)
- Address space pollution: Each piece of dynamic code requires a unique contract address, leading to blockchain state bloat
- 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
-
Input Parameters: Takes seven stack parameters (identical to DELEGATECALL interface):
gas: Amount of gas to provide to the dynamic executioncodeOffset: Memory offset where bytecode startscodeLength: Length of bytecode in bytesargsOffset: Memory offset of input data to pass to dynamic codeargsLength: Length of input data in bytesretOffset: Memory offset to store return dataretLength: Maximum length of return data to copy back
-
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
-
Return Mechanism:
- Dynamic code terminates with
RETURN(success) orREVERT(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
RETURNDATASIZEopcode
- Dynamic code terminates with
-
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
- Storage operations (
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
DELEGATECALLbase 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
DELEGATECALLto 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
- Parameter validation: Pop seven parameters from stack and validate memory ranges
- Gas calculation: Apply 63/64 rule to determine maximum allocatable gas
- Context preparation: Create isolated execution stack and copy input data to new calldata
- Dynamic execution: Execute bytecode with gas limit, maintaining calling contract's context
- Result handling: On completion, copy return data and push success status to caller's stack
- 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
-
DELEGATECALL-compatible interface: Using identical parameter structure ensures familiar semantics for developers and enables drop-in replacement for many
DELEGATECALLpatterns. -
Isolated execution stack: Prevents stack contamination while enabling proper argument passing and return value handling, maintaining clean separation between caller and dynamic code.
-
Explicit gas control: Provides fine-grained control over execution costs and prevents dynamic code from consuming all available gas unexpectedly.
-
Memory-based I/O: Using memory for arguments and return values follows established EVM patterns and provides maximum flexibility for data exchange.
-
Context preservation: Maintaining identical execution context to
DELEGATECALLensures compatibility with existing patterns while providing gas savings. -
Full opcode compatibility: Allowing all opcodes including recursive
RUNCODEmaintains consistency withDELEGATECALLbehavior 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 0x60Cold 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 savingsGas 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 rulesReference Implementation
A reference implementation requires the following EVM modifications:
- Opcode registration: Add
0xF6opcode withRUNCODEhandler in the interpreter - Parameter parsing: Extract seven parameters from execution stack with proper validation
- Gas calculation: Apply 63/64 rule for maximum gas allocation
- Context isolation: Create separate execution stack while preserving contract context
- Calldata simulation: Copy input arguments to new execution context's calldata
- Gas management: Implement gas limiting, accounting, and unused gas refunding
- Result handling: Copy return data and provide success/failure status
- 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
-
Arbitrary code execution: Dynamic bytecode execution introduces eval()-like capabilities, enabling arbitrary contract behavior modification at runtime
-
Reentrancy amplification: Dynamic code can create new and unpredictable reentrancy attack vectors, especially when combined with external calls
-
Gas griefing: Malicious actors could craft bytecode designed to consume maximum gas while providing minimal utility
-
State manipulation: Dynamic code has full access to contract storage, enabling potentially destructive state modifications
-
Audit complexity: Contracts using
RUNCODEbecome significantly harder to audit and formally verify due to runtime code generation
Mitigations
-
Gas limiting: Standard EVM gas mechanics apply to dynamic execution, preventing runaway computation and providing cost predictability
-
63/64 rule: Standard EVM gas forwarding rules apply, preventing the calling context from being completely drained of gas
-
Call depth limits: EVM's built-in 1024-level call depth limit naturally prevents excessive recursion in
RUNCODEcalls -
Context isolation: Execution within the calling contract's context limits the blast radius of malicious operations to the calling contract's state
Security Recommendations
-
Input validation: Contracts using
RUNCODEshould implement strict validation of bytecode before execution, potentially using allowlists for permitted instruction patterns -
Access control: Implement appropriate authorization mechanisms around
RUNCODEusage to prevent unauthorized dynamic code execution -
Audit practices: Dynamic code generation and execution logic requires thorough security auditing with particular attention to input validation and state modification patterns
-
Gas budgeting: Use conservative gas limits for dynamic execution to prevent excessive resource consumption and ensure predictable costs
-
Recursion awareness: When allowing recursive patterns, ensure proper gas budgeting to prevent call depth or out-of-gas issues
-
State protection: Consider implementing state snapshots or rollback mechanisms for critical contract state when using dynamic code execution
-
Monitoring: Implement comprehensive logging and monitoring for
RUNCODEusage 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
Copyright and related rights waived via CC0.
