EIP-XXXX: Invariant Layout Guard Opcode
Ethereum Magicians
Simple Summary Introduce a protocol-level state safety mechanism through a new opcode. 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. Example: import "https://github.com/Helkomine/invariant-guard/blob/main/invariant-guard/InvariantGuardInternal.sol"; import "@openzeppelin/contracts/utils/Address.sol"; contract InvariantSimple is InvariantGuardInternal { address owner; // Invariants cannot be applied to mapping with InvariantGuard. mapping(address => uint256) balances; function safeDelegateCall(address target, bytes calldata data) public payable invariantStorage(_getSlot()) { Address.functionDelegateCall(target, data); } function _getSlot() internal pure returns (bytes32[] memory slots) { bytes32 slot; assembly { slot := owner.slot } slots = new bytes32[](1); slots[0] = slot; } } PR: 1 post - 1 participant Read full topic