--- eip: 8089 title: Smart Contract Lifecycle Registry description: A registry for tracking smart contract lifecycle status. author: Kenny Kung (@kennyk10) , Jeff Fei (@77eff) , Shepherd Shu (@baishuo13) discussions-to: https://ethereum-magicians.org/t/erc-8089-smart-contract-lifecycle-registry/26641 status: Draft type: Standards Track category: ERC created: 2025-11-20 --- ## Abstract This EIP introduces a minimal registry interface for enforcing maintenance of smart contract lifecycle status. The status indicates whether a smart contract deployed is Active (in use), Deprecated (functional but unmaintained), Frozen (paused but reversible), or Terminated (permanently deactivated). The registry allows implementers to record and update the status and important metadata about contracts. The goal is to improve security and simplify inventory management for protocols by providing a minimal, interoperable mechanism for users and tools (e.g., block explorers) to monitor contract states. ## Motivation The absence of standardized tracking in the smart contract lifecycle introduces critical challenges for Ethereum entities: - Loss of Inventory: Entities cannot enumerate or monitor deployed contracts and avoid maintaining contract inventories due to perceived inconvenience, leading to disorganized asset management. - Security Risks: Without an inventory, entities cannot promptly identify their deployed smart contracts and respond quickly to attacks due to discrepancy of statuses. - Inefficient Status Validation: Security researchers require inefficient coordination with teams to discover discrepancies of smart contract state (e.g., contracts marked inactive internally but still callable). - Lack of Interoperable Tracking: Custom or off-chain registries lack interoperability, isolating contract data and limiting integration with ecosystem tools. ## 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](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174). The registry MUST include the following elements: ### Interface The `IContractRegistry` interface defines the standard functions and events for the registry, ensuring interoperability and consistency across implementation. ```solidity /// @title Smart Contract Lifecycle Registry pragma solidity ^0.8.0; interface IContractRegistry { enum ContractStatus { Default, Active, Deprecated, Frozen, Terminated } struct ContractMetadata { ContractStatus status; string extraData; uint256 statusUpdateTimestamp; } // MUST trigger when a new contract is added to the registry event ContractRegistered(address indexed contractAddress); // MUST trigger when a registered contract’s status field is modified event ContractStatusModified(address indexed contractAddress, ContractStatus oldStatus, ContractStatus newStatus); // MUST trigger when a registered contract’s extraData field is modified event ContractExtraDataModified(address indexed contractAddress, string newExtraData, string newExtraData); // MUST trigger when a contract is removed from the registry event ContractRemoved(address indexed contractAddress); // MUST be called only by AUTHORIZED users (e.g., the registry owner) to add a new contract to the registry and MUST emit the ContractRegistered event function registerContract(address contractAddress, ContractStatus status, string memory extraData) external; // MUST be called only by AUTHORIZED users to modify a registered contract’s status and MUST emit the ContractStatusModified event function modifyContractStatus(address contractAddress, ContractStatus newStatus) external; // MUST be called only by AUTHORIZED users to modify a registered contract’s extraData and MUST emit the ContractExtraDataModified event function modifyContractExtraData(address contractAddress, string newExtraData) external; // MUST called only by AUTHORIZED users to remove a contract from the registry and MUST emit the ContractRemoved event function removeContract(address contractAddress) external; // Provides public read-only access to a contract’s metadata to inspect its current status, extraData, and timestamps function getContractMetadata(address contractAddress) external view returns (ContractMetadata memory); // Returns a list of all registered contract addresses, enabling to enumerate the entire inventory of contracts managed by the registry function getAllContracts() external view returns (address[] memory); } ``` ### Data Structures The `ContractStatus` enum defines possible contract states to indicate the operational status of a registered contract. The enum values are: - `Default`: Default value for uninitialized contract status. - `Active`: The contract is actively used, fully functional, and intended for regular operation. Suitable for contracts in production, signaling safe interaction to users. - `Deprecated`: The contract is no longer actively used or recommended but remains fully functional, allowing user interactions (e.g., Outdated contract version but the contract is still callable by users). - `Frozen`: The contract cannot be used due to a temporary, reversible halt (e.g., via a pause mechanism). Used for signaling temporary unavailability with potential reactivation (e.g., The contract is paused and can't be interacted with temporarily but it can be recovered to fully operational status by unpausing the contract). - `Terminated`: The contract cannot be used, and its usability is not recoverable. Used for obsolete or abandoned contracts to signal they should be ignored or archived (e.g., The contract is paused and revoked pauser role, resulting no one can revert the pause state and the contracts is completely not callable). The `ContractMetadata` struct stores metadata for each contract, including: - `status`: The current operational status, REQUIRED to track lifecycle state. - `extraData`: An extra data field to store additional information of the contract, OPTIONAL for context (e.g., IPFS storing additional data to store details of audit reports, proxy implementation address, etc). - `statusUpdateTimestamp`: The block timestamp of the last modification time of status, REQUIRED to track maintain auditability. ### Implementation Requirements - Access Control: MUST restrict `registerContract()`, `modifyContractStatus()`, `modifyContractExtraData()` and `removeContract()` to authorized addresses. - Events: MUST emit events for all state changes to enable off-chain indexing by tools like block explorers. - Implementers SHOULD deploy one registry per entity to manage their contracts, ensuring scalability and customization. ## Rationale The design choices reflect the following considerations: - Simplified `ContractMetadata` Struct: The `ContractMetadata` struct includes only `status`, `extraData` and `statusUpdateTimestamp` to keep the registry lightweight and focused on tracking contract deployment and lifecycle states. This minimizes gas costs and adoption barriers. - Exclusion of Privileged Accounts: Fields for tracking ownership (e.g., `owner`) were considered but excluded due to the complexity of smart contract ownership models. Not all contracts have a single privileged account. Tracking privileged accounts accurately requires a dedicated registry to handle diverse models, which is better addressed in a separate EIP. - Exclusion of Upgrade-Specific Fields: Fields like `upgradeProxy` were considered but removed due to the diversity of upgrade patterns (e.g., [EIP-1967](./eip-1967.md) with a single implementation vs. [EIP-2535](./eip-2535.md) Diamonds with multiple facets). Instead, users can register each implementation contract (e.g., proxies, facets) as separate entries in the registry, using the `extraData` field to clarify relationships. This approach ensures flexibility for all upgradeable contract types while keeping the struct simple. - Flexible Extra Data Field: The `extraData` field also allows users to provide context for complex scenarios, such as proxy implementations, without imposing rigid fields that may not suit all contracts. These extra data can also be any form like an IPFS link which stored extra information of the contracts. - Exclusion of a Global Registry: A global registry per chain was considered but rejected due to governance challenges. A single registry would require centralized or complex decentralized governance to manage updates, risking centralization, disputes, or unauthorized changes. Instead, this EIP prefers each entity deploy their own registry, ensuring autonomous control over their contract metadata and avoiding governance-related risks. However, global registry could also be implemented if a legitimate governance to manage updates can be ensured. ## Backwards Compatibility No backward compatibility issues are introduced. This EIP introduces a new registry contract and does not affect existing Ethereum standards or contracts. ## Reference Implementation The reference implementation of the `ContractRegistry` provided in this EIP is for illustrative purposes only, demonstrating the proposed interface and functionality. Below is a reference implementation of the registry contract in Solidity: ```solidity pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; /// @title EIP8089 Implementation /// @notice Implementation of EIP-8089 Smart Contract Lifecycle Registry Standard /// @dev This registry allows operators to record and update metadata about contracts, including their operational status contract ContractRegistry is Ownable { using EnumerableSet for EnumerableSet.AddressSet; /// @notice Status of a registered contract /// @dev Used to track the lifecycle status of registered contracts enum ContractStatus { Default, /// Initial or reset state Active, /// Contract is operational Deprecated, /// Contract is outdated but still functional Frozen, /// Contract operations are temporarily suspended but recoverable Terminated /// Contract is permanently disabled and not recoverable } /// @notice Metadata associated with a registered contract /// @dev Contains status, extra data, and timestamp information struct ContractMetadata { ContractStatus status; /// Current status of the contract string extraData; /// Additional information about the contract uint256 statusUpdateTimestamp; /// Timestamp of last status update } /// @dev Mapping of contract addresses to their metadata mapping(address => ContractMetadata) private _metadata; /// @dev Set of all registered contract addresses EnumerableSet.AddressSet private _contracts; /// @notice Emitted when a new contract is registered /// @param contractAddress The address of the registered contract event ContractRegistered(address indexed contractAddress); /// @notice Emitted when a contract's status is modified /// @param contractAddress The address of the contract /// @param oldStatus The previous status of the contract /// @param newStatus The new status of the contract event ContractStatusModified(address indexed contractAddress, ContractStatus oldStatus, ContractStatus newStatus); /// @notice Emitted when a contract's extra data is modified /// @param contractAddress The address of the contract /// @param oldExtraData The previous extra data /// @param newExtraData The new extra data event ContractExtraDataModified(address indexed contractAddress, string oldExtraData, string newExtraData); /// @notice Emitted when a contract is removed from the registry /// @param contractAddress The address of the removed contract event ContractRemoved(address indexed contractAddress); /// @dev Maximum length for extraData field (256 bytes) uint256 private constant MAX_EXTRADATA_LENGTH = 256; /// @notice Constructs the ContractRegistry contract /// @param initialOwner The address that will be set as the owner of the contract constructor(address initialOwner) Ownable(initialOwner) {} /// @dev Private function to update contract status and emit ContractStatusModified event /// @param contractAddress The address of the contract to update /// @param newStatus The new status to assign to the contract function _modifyContractStatus(address contractAddress, ContractStatus newStatus) private { ContractStatus oldStatus = _metadata[contractAddress].status; _metadata[contractAddress].status = newStatus; _metadata[contractAddress].statusUpdateTimestamp = block.timestamp; emit ContractStatusModified(contractAddress, oldStatus, newStatus); } /// @dev Private function to update extra data field and emit ContractExtraDataModified event /// @param contractAddress The address of the contract to update /// @param newExtraData The new extra data to assign to the contract function _modifyContractExtraData(address contractAddress, string memory newExtraData) private { string memory oldExtraData = _metadata[contractAddress].extraData; require(bytes(newExtraData).length <= MAX_EXTRADATA_LENGTH, "ExtraData too long"); _metadata[contractAddress].extraData = newExtraData; emit ContractExtraDataModified(contractAddress, oldExtraData, newExtraData); } /// @notice Registers a new contract in the registry /// @dev Only callable by the owner /// @param contractAddress The address of the contract to register /// @param newStatus The initial status to assign to the contract /// @param newExtraData Additional information about the contract function registerContract( address contractAddress, ContractStatus newStatus, string calldata newExtraData ) public onlyOwner { require(contractAddress != address(0), "Invalid address"); require(!_contracts.contains(contractAddress), "Contract already registered"); _contracts.add(contractAddress); _modifyContractStatus(contractAddress, newStatus); _modifyContractExtraData(contractAddress, newExtraData); emit ContractRegistered(contractAddress); } /// @notice Updates the status of a registered contract /// @dev Only callable by the owner /// @param contractAddress The address of the contract to update /// @param newStatus The new status to assign to the contract function modifyContractStatus( address contractAddress, ContractStatus newStatus ) public onlyOwner { require(_contracts.contains(contractAddress), "Contract not registered"); require(newStatus != ContractStatus.Default, "Invalid contract status"); _modifyContractStatus(contractAddress, newStatus); } /// @notice Updates the extra data of a registered contract /// @dev Only callable by the owner /// @param contractAddress The address of the contract to update /// @param newExtraData The new extra data to assign to the contract function modifyContractExtraData( address contractAddress, string calldata newExtraData ) public onlyOwner { require(_contracts.contains(contractAddress), "Contract not registered"); _modifyContractExtraData(contractAddress, newExtraData); } /// @notice Removes a contract from the registry /// @dev Only callable by the owner. Resets contract metadata to default values. /// @param contractAddress The address of the contract to remove function removeContract(address contractAddress) public onlyOwner { require(_contracts.contains(contractAddress), "Contract not registered"); _contracts.remove(contractAddress); _modifyContractStatus(contractAddress, ContractStatus.Default); _modifyContractExtraData(contractAddress, ""); emit ContractRemoved(contractAddress); } /// @notice Retrieves the metadata for a registered contract /// @dev Reverts if the contract is not registered /// @param contractAddress The address of the contract /// @return The metadata associated with the contract function getContractMetadata(address contractAddress) public view returns (ContractMetadata memory) { require(_contracts.contains(contractAddress), "Contract not registered"); return _metadata[contractAddress]; } /// @notice Retrieves all registered contract addresses /// @return An array of all registered contract addresses function getAllContracts() public view returns (address[] memory) { return _contracts.values(); } } ``` ## Security Considerations - Access Control: The registry’s write functions are a critical attack surface. If access control is misconfigured or compromised, an attacker could falsely mark active contracts as `Terminated` or register fraudulent entries, misleading users and dependent tools. - Data Integrity: The registry does not verify the accuracy of metadata it stores. A registry owner could intentionally or accidentally record incorrect statuses or misleading `extraData`, causing users to interact with unsafe or obsolete contracts. - State Transition Validation: The base interface does not enforce valid state transitions. Without additional guards, a `Terminated` contract could be reactivated. - Off-Chain Verification for Sensitive Statuses: The `Terminated` status signals permanent deactivation. If applied without sufficient evidence, it could incorrectly signal that a live contract is defunct, or conversely, delay marking a genuinely compromised contract. The `extraData` field can serve as an audit trail by referencing supporting off-chain documentation. - Legitimacy of Registry: Anyone can deploy a contract implementing this interface. A malicious actor could create a fake registry that misrepresents the status of contracts they do not own, deceiving users or block explorers. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).