Skip to content

Helkomine/BRIDGE_PROJECT

Repository files navigation

eip 9999
title Invariant Layout Guard Opcode
description Introduce a protocol-level state safety mechanism through a new opcode.
author Helkomine (@Helkomine)
discussions-to https://ethereum-magicians.org/t/eip-xxxx-invariant-layout-guard-opcode/27714
status Draft
type Standards Track
category Core
created 2026-02-11

Abstract

This EIP introduces a new opcode, MUTABLE, which restricts state changes to an explicitly defined scope. Any attempt to modify state outside the permitted scope MUST cause execution to revert.

Motivation

Unintended state mutation during execution is a persistent security risk in smart contract systems. This risk is especially pronounced in proxy-based architectures that rely on DELEGATECALL, where the calling contract effectively relinquishes control over which state changes may occur in the callee’s execution context. At the protocol level, there is currently no mechanism to stabilize or constrain state layout during execution. Introducing such a mechanism enables safer composition of contracts, supports future extensibility, and improves overall robustness for an increasingly dynamic Layer 1 ecosystem.
At present, there exist contract-level approaches for constraining state mutation, which we refer to as an inner guard. These mechanisms offer strong and programmable protection for explicitly declared locations but fundamentally cannot defend against mutations outside the specified set. Because the number of potentially mutable locations is unbounded, attempting full coverage at the contract level is infeasible under current gas constraints. To achieve comprehensive protection, a complementary outer guard is required. This can only be implemented at the protocol level. By combining an inner guard with the proposed outer guard, contracts can form a robust firewall against unintended or malicious side effects arising from external calls.

Specification

Constants

BASE_OPCODE_COST : 3

Opcode

MUTABLE (0x2f)

Stack input:

  • offset: Memory offset at which the RLP-encoded data begins.
  • length: Maximum number of bytes that may be read from memory.
  • isGuard: Boolean flag indicating whether the guard mechanism is enabled.

guardOrigin

The guardOrigin variable tracks the origin and propagation of mutation restrictions throughout execution frames within a transaction. It SHALL take one of the following values:

  • NONE: No mutation restrictions are active.
  • LOCAL: Mutation restrictions are active and were established in the current execution frame.
  • INHERITED: Mutation restrictions are active and were inherited from a parent execution frame.

RLP Data Structures

The MUTABLE opcode interprets RLP-encoded data located in the caller’s memory.

Type aliases

  • Option: uint8
  • Allowed: bool
  • Address: bytes20
  • AllowedStorage: bytes32
  • AllowedTransientStorage: bytes32

Option values

Option is a uint8 value with the following meanings:

  • 0x00: Code
  • 0x01: Nonce
  • 0x02: Balance
  • 0x03: Storage
  • 0x04: TransientStorage

Payload interpretation

The Payload field SHALL be interpreted according to the associated Option value:

  • For 0x00, 0x01, 0x02: the payload MUST be empty.
  • For 0x03: the payload MUST be an RLP list of AllowedStorage values.
  • For 0x04: the payload MUST be an RLP list of AllowedTransientStorage values.

Structures

  • PolicyEntry:
[ Option, Allowed, Payload ]
  • MutableSet:
[ Address, List[PolicyEntry] ]
  • MutableSetList:
List[MutableSet]

If multiple PolicyEntry elements for the same Option appear within a MutableSet, only the last occurrence in RLP order SHALL be applied. If multiple MutableSet elements reference the same Address, they SHALL be processed in RLP order, with later entries overriding earlier ones.

Semantics

Transaction initialization

At the start of transaction execution, guardOrigin SHALL be initialized to NONE, and the effective MutableSetList SHALL be empty in the top-level execution frame.

Propagation of mutation restrictions

When an execution frame spawns a child frame via CALL, DELEGATECALL, CALLCODE, STATICCALL, CREATE, or CREATE2, both guardOrigin and the effective MutableSetList SHALL be propagated as follows:

  • If guardOrigin is NONE, the child frame SHALL receive guardOrigin = NONE.
  • If guardOrigin is LOCAL or INHERITED, the child frame SHALL receive guardOrigin = INHERITED.

Execution of the MUTABLE opcode

When executing the MUTABLE opcode, the EVM SHALL decode the RLP-encoded MutableSetList from memory starting at offset, reading at most length bytes. If isGuard is set to false, the EVM SHALL set guardOrigin to NONE only if the current value of guardOrigin is NONE or LOCAL. If guardOrigin is INHERITED, this operation SHALL have no effect. If isGuard is set to true:

  • If guardOrigin is NONE or LOCAL, the decoded MutableSetList SHALL replace the current MutableSetList, and guardOrigin SHALL be set to LOCAL.
  • If guardOrigin is INHERITED, the effective MutableSetList SHALL be computed as the intersection of the decoded MutableSetList and the inherited MutableSetList from the parent execution frame, and guardOrigin SHALL remain INHERITED.

Enforcement of mutation invariants

While executing an execution frame where guardOrigin is LOCAL or INHERITED, any instruction that attempts to mutate state outside the permitted scope defined by the effective MutableSetList SHALL result in an exceptional halt equivalent to an out-of-gas condition. The following operations SHALL always result in an exceptional halt when guardOrigin is not NONE:

  • Execution of CALLCODE.
  • Execution of SELFDESTRUCT.

The following operations SHALL result in an exceptional halt unless explicitly permitted by the effective MutableSetList:

  • CALL with a non-zero value, unless mutation of Balance is permitted for both the caller address and the target address.
  • CREATE or CREATE2, unless mutation of Code and Nonce is permitted for the created address, and mutation of Nonce is permitted for the caller address. If a non-zero value is transferred, mutation of Balance MUST also be permitted for both addresses.
  • SSTORE, unless mutation of Storage is permitted for the executing address and the target storage slot is explicitly allowed.
  • TSTORE, unless mutation of TransientStorage is permitted for the executing address and the target transient storage slot is explicitly allowed.

Any future opcode or protocol change that introduces new forms of state mutation MUST define its interaction with MUTABLE in a manner consistent with the enforcement rules defined herein.

Exceptional conditions

Execution of MUTABLE SHALL result in an exceptional halt if any of the following occur:

  • Out-of-gas.
  • Insufficient stack items.
  • Any attempt to decode RLP data that would require reading beyond length bytes.
  • The RLP payload does not conform to the MutableSetList structure defined in this specification.

Gas cost

The gas cost of the MUTABLE opcode consists of:

  • The base opcode cost BASE_OPCODE_COST.
  • Memory expansion costs, calculated according to existing EVM rules.
  • A per-chunk (32-byte) cost proportional to length, to account for RLP decoding overhead.

Rationale

Design Intent

The initial design goal was to enable safe and transparent use of modular smart contracts, allowing modules to be freely integrated while preserving the safety guarantees of the host contract. Based on this idea, we developed InvariantGuard, a contract-level pattern that provides powerful and flexible modifiers for protecting state with fine-grained control. However, this and similar patterns were found to be inherently limited: they cannot protect unspecified state locations, and the number of such locations is too large to enumerate exhaustively. Therefore, achieving full state coverage requires direct support from the protocol. The MUTABLE opcode addresses this gap by enforcing invariants at the execution environment level.

No Prohibition Under STATICCALL

The MUTABLE opcode only configures execution constraints and does not itself introduce new state changes. As such, there is no technical justification for prohibiting its use in a STATICCALL execution context.

Minimal Handling of CALLCODE and SELFDESTRUCT

Both CALLCODE and SELFDESTRUCT are considered deprecated. Consequently, only minimal checks are applied to these opcodes to ensure that no unintended bypass of MUTABLE constraints is possible.

Extensible Option Design

The MutableSetList structure is explicitly designed to be extensible. This allows new invariant types or state categories to be added in the future without breaking contracts that rely on earlier versions of the MUTABLE opcode, and without introducing significant additional gas costs.

length as a Maximum Size Hint

Since RLP encoding is self-describing, the length parameter serves solely as a hint for clients to quickly determine an upper bound on the data size when decoding a MutableSetList. It does not alter semantic interpretation.

Allowing Option Overrides

Allowing later options to override earlier ones preserves flexibility and reduces the likelihood of unexpected failures caused by minor execution changes.

No Threshold Limits

Introducing threshold limits for disallowed state locations provides no clear benefit. For allowed locations, programmable limits can already be effectively enforced using contract-level patterns such as InvariantGuard.

Additional Parsing Cost for MUTABLE

An additional gas cost is introduced to account for RLP parsing and invariant management throughout execution. This cost is comparable to the JUMPDEST analysis cost applied to init_code during contract creation.

Backwards Compatibility

Opcode Behavior Changes

State-affecting opcodes are constrained by MUTABLE in a manner analogous to their behavior under STATICCALL. These effects are strictly transaction-scoped and do not alter the intrinsic semantics of the opcodes outside the configured execution environment.

Upgrade Compatibility

Because MutableSetList relies on extensible Option encoding, future additions to the invariant system do not change the behavior of contracts that use earlier versions of MUTABLE.

Test Cases

To be announced.

Reference Implementation

To be announced.

Security Considerations

Safety–Flexibility Tradeoff

For maximum protection, contracts must precisely specify all permitted state mutations. This is a non-trivial task for most dApps. Accordingly, this EIP allows dApps to omit fine-grained checks on individual storage and transient storage slots to simplify integration. In such cases, wallets or dApps are expected to assess whether the target contract is sufficiently safe for user interaction. While this approach carries higher risk than exact slot-level enforcement, the risk can be mitigated through rigorous contract auditing.

Compiler Support

Compilers should provide ergonomic high-level access to state under invariant constraints. At present, Solidity does not support complex transient storage variables, which significantly limits the usability of advanced safety mechanisms. Close coordination between protocol upgrades and compiler enhancements is therefore essential to fully realize the security benefits of this design.

Copyright

Copyright and related rights waived via CC0.

About

Read ct

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors