diff --git a/ERCS/erc-7625.md b/ERCS/erc-7625.md new file mode 100644 index 0000000000..073ccac4fa --- /dev/null +++ b/ERCS/erc-7625.md @@ -0,0 +1,577 @@ +--- +eip: 7625 +title: Smart Contract Identifiers and Metadata Extension +description: Proposes an interface for contractId's and metadata at the smart contract level. +author: Larry V. Kłosowski (@SaulBuilds) +discussions-to: https://ethereum-magicians.org/t/erc-7625-smart-contract-id-tokenization-standard/18742 +status: Draft +type: Standards Track +category: ERC +created: 2024-02-02 +--- + +## Abstract + +This document outlines a proposed framework aimed at enhancing the Ethereum ecosystem's smart contract factories and the handling of Smart Contract Transfers and data. By adopting principles akin to those used for non-fungible tokens (NFTs), this framework intends to manage the lifecycle of smart contracts from their deployment to their integration into digital marketplaces. It introduces a method for tokenizing or ‘mobilizing’ smart contracts, associating them with unique identifiers and metadata. This initiative is designed to improve the discoverability, manageability, and interoperability of smart contracts, creating a a more secure ownership transfer and seamless dApp interactions. Ultimately, the framework seeks to further the tokenization of assets and build a more connected and efficient Ethereum ecosystem. + + + +## Motivation + +Integrating the concept of smart contracts as mintable or registerable tokens with unique identifiers (contractIds) can greatly enhance the functionality and efficiency of various applications, from art drops to supply chain management and legal contracts. There are a broad set of novel use cases that would create the value to make the gas cost of contract creation reasonable on mainnet and more than feasible on layer2 infrastrucure where gas is cheaper per block. + +Here’s a deeper look into these use cases and applications, the aim is to highlight the motivations for different devs to implement contractIds and the creation of both fungible and Non-fungible versions of smart contracts: + +### Enhanced Functionality for Art Drops: +- **Unique Art Experiences**: Utilizing contractIds, artists can create unique, serialized art drops where each piece or collection is associated with a distinct contract. This not only authenticates the art but also enables special functionalities such as unlocking private content, redeemable experiences, or evolving art based on ownership history. +- **Community Engagement**: ContractIds can facilitate community-driven features, such as voting rights on future projects or decentralized curation of art exhibitions, by leveraging the non-fungible nature of the contracts to represent membership or participation rights. + +### Ease of Tracking in Supply Chain: +- **Provenance and Authenticity**: Each item in the supply chain can be associated with a unique contractId, enabling transparent tracking of its origin, manufacturing process, and distribution journey. This not only assures consumers of the product's authenticity but also simplifies recall processes if needed. +- **Efficient Logistics**: By tokenizing assets as fungible or non-fungible tokens based on their nature, companies can automate and streamline logistics operations, from inventory management to shipping and receiving, leveraging smart contracts for real-time updates and actions. + +### Pre-packaged Legal Contracts: +- **Automated Legal Agreements**: Legal documents such as leases, loan agreements, or incorporation papers can be standardized and sold as pre-packaged smart contracts. Each contractId represents a unique agreement, customizable through a set of parameters defined at the time of purchase or activation. +- **Seamless Integration into Business Processes**: Businesses can integrate these tokenized legal contracts into their operations, automating processes such as contract execution, compliance checks, and renewals. The fungible or non-fungible nature of these contracts, coupled with unique contractIds, ensures easy management and verification across stakeholders. + +### General Implications: +- **Non-Fungible Smart Contracts for Unique Assets**: Utilizing NFTs for unique assets or rights (e.g., one-of-a-kind artworks, real estate, or intellectual property) provides a clear, immutable record of ownership and transaction history, enhancing trust and liquidity in these markets. +- **Fungible Smart Contracts for Divisible Assets**: Fungible tokens, managed through smart contracts with specific contractIds, are ideal for assets that require divisibility and uniformity, such as shares in a company, commodities, or digital currencies. This facilitates ease of trading and integration into financial systems. + +By tying every asset, right, or agreement back to a unique or fungible smart contract managed through specific contractIds, the proposed framework significantly enhances the functionality, security, and efficiency of digital and physical asset management. This approach not only simplifies the tracking and transfer of assets across various domains but also opens up new avenues for innovation, customization, and engagement in digital marketplaces and beyond. + +## Specification + +This contract factory framework proposes a structured system for the tokenization and management of smart contracts within the Ethereum ecosystem. This system introduces a set of interfaces and functionalities designed to standardize the assignment of unique identifiers (contractIds) to smart contracts and to manage associated metadata, enabling enhanced interoperability, management, and discoverability of smart contracts. + +###Interface + +The core of this framework is defined by the interface, which extends it to ensure compliance with the Ethereum standard for interface detection. + +#### Key Functions + +1. **Asset Locking and Unlocking**: +`lockAssetTransfers(uint256 contractId)`: Locks the asset transfers for a specific contractId, preventing any changes until explicitly unlocked. + `unlockAssetTransfers(uint256 contractId)`: Unlocks the asset transfers for a specific contractId, allowing changes to be made. + +2. **Contract Ownership Management**: + `balanceOfContractId(address owner)`: Returns the number of contracts owned by a specific address. + `ownerOfContractId(uint256 contractId)`: Determines the owner of a specific contractId. + +3. **Contract Transfer**: +`safeContractTransferFrom(address from, address to, uint256 contractId, bytes calldata data)`: Safely transfers a contractId from one address to another, optionally including additional data for the receiver. + +4. **Operator Approval**: +`approveOperatorToTransfer(address approved, uint256 contractId)`: Approves an operator to transfer a specific contractId. +`setApprovalForAllContracts(address operator, bool approved)`: Sets or revokes an operator's approval to manage all of an owner's contractIds. + `getApproved(uint256 contractId)`: Gets the approved operator for a specific contractId. + +5. **Contract Creation and Metadata Management**: + `createContract()`: Creates a new contract instance and assigns it a unique contractId. `withdraw(address to, uint256 amount)`: Enables the withdrawal of funds from the contract. + +6. **Receiving Contracts**: +`onContractReceived(address operator, address from, uint256 contractId, bytes calldata data)`: Handles the receipt of a contractId, implementing custom logic as necessary. + +### Metadata Extension + +For managing contract metadata, the Metadata interface extension allows linking a URI to a contractId, enabling the retrieval of detailed metadata associated with the contract. + +`tokenURI(uint256 contractId)`: Returns the URI containing metadata for a specific contractId. + +### Implementation Considerations + +Implementers of this standard must consider security measures, such as reentrancy guards, validation of contractIds, and proper handling of the `data` payload in transfers. The implementation must also ensure that contract locks are respected, preventing unauthorized transfers or modifications. + +Contracts wishing to receive contractIds must implement the `ierc-7625Receiver` interface and handle incoming transfers in a manner that aligns with their specific application requirements. + +```solidity +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "../lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; +import "../lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol"; + + + +/** + * @title Interface for ERC7625 Smart Contract Identification and Management + * @dev Extends ERC165. Defines an interface for managing unique smart contract IDs and integrates with ERC20 and ERC721 for asset tracking. + */ +interface IERC7625 is IERC165 { + /** + * @dev Emitted when a contract's asset transfers are locked or unlocked. + * @param owner Address of the contract owner. + * @param locked Status of the asset lock (true if locked). + */ + event AssetsLocked(address indexed owner, bool locked); + + /** + * @dev Emitted upon the transfer of a contract from one address to another. + * @param from Address of the current owner. + * @param to Address of the new owner. + * @param contractId Unique identifier of the contract being transferred. + */ + event TransferContract(address indexed from, address indexed to, uint256 indexed contractId); + + /** + * @dev Emitted when a new operator is approved to manage a specific contract ID. + * @param owner Address of the contract owner. + * @param approved Address of the approved operator. + * @param contractId Unique identifier of the contract. + */ + event ApprovalForTransfer(address indexed owner, address indexed approved, uint256 indexed contractId); + + /** + * @dev Emitted when an operator is granted or revoked permission to manage all contracts of an owner. + * @param owner Address of the contract owner. + * @param operator Address of the operator. + * @param approved True if the operator is approved, false otherwise. + */ + event ApprovalForTransferOfAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Emitted upon withdrawal of funds, specifying the beneficiary and the amount withdrawn. + * @param to Beneficiary address. + * @param amount Amount of funds withdrawn. + */ + event Withdraw(address indexed to, uint256 amount); + + /** + * @notice Locks all asset transfers and withdrawals for a given contract ID. + * @param contractId Unique identifier of the contract. + */ + function _lockAssetTransfers(uint256 contractId) external; + + /** + * @notice Unlocks all asset transfers and withdrawals for a given contract ID. + * @param contractId Unique identifier of the contract. + */ + function _unlockAssetTransfers(uint256 contractId) external; + + /** + * @notice Returns the number of contracts owned by a given address. + * @param owner Address to query the balance for. + * @return Number of contracts owned. + */ + function balanceOfContractId(address owner) external view returns (uint256); + + /** + * @notice Returns the owner of a specified contract ID. + * @param contractId Unique identifier of the contract. + * @return Address of the owner. + */ + function ownerOfContractId(uint256 contractId) external view returns (address); + + /** + * @notice Transfers a contract to another address along with additional data. + * @param from Current owner of the contract. + * @param to Recipient address. + * @param contractId Unique identifier of the contract. + * @param data Additional transfer data. + */ + function safeContractTransferFrom(address from, address to, uint256 contractId, bytes calldata data) external payable; + + /** + * @notice Approves another address to manage the specified contract. + * @param approved Address to be approved. + * @param contractId Unique identifier of the contract. + */ + function approveOperatorToTransfer(address approved, uint256 contractId) external payable; + + /** + * @notice Sets or revokes approval for an operator to manage all contracts of the sender. + * @param operator Address of the operator. + * @param approved Approval status. + */ + function setApprovalForAllContracts(address operator, bool approved) external; + + /** + * @notice Returns the approved address for a specified contract ID. + * @param contractId Unique identifier of the contract. + * @return Address currently approved. + */ + function getApproved(uint256 contractId) external view returns (address); + + /** + * @notice Withdraws funds from the contract to a specified beneficiary. + * @param to Beneficiary address. + * @param amount Amount to withdraw. + */ + function withdraw(address to, uint256 amount) external; + + /** + * @notice Creates a new contract instance and returns its unique ID. + * @return Unique ID of the newly created contract instance. + */ + function createContract(bytes calldata metadata, bytes32 salt) external returns (uint256); + + /** + * @notice Handles the receipt of an ERC7625 contract. + * @param operator Address that initiated the transfer. + * @param from Previous owner of the contract. + * @param contractId Unique identifier of the contract being transferred. + * @param data Additional data with no specific format. + * @return Indicator for successful receipt. + */ + function onContractReceived(address operator, address from, uint256 contractId, bytes calldata data) external returns(bytes4); +} + +/** + * @title Receiver Interface for ERC7625 Smart Contract Identification and Management + * @dev Interface for contracts wishing to support safe transfers of ERC7625 contract IDs. + */ +interface IERC7625Receiver { + /** + * @notice Handles the receipt of a contract ID. + * @param operator Address that initiated the transfer. + * @param from Previous owner of the contract. + * @param contractId Unique identifier of the contract being transferred. + * @param data Additional data sent along with the transfer. + * @return bytes4 Returns `bytes4(keccak256("onERC7625Received(address,address,uint256,bytes)"))` when the transfer is accepted. + */ + function onERC7625Received(address operator, address from, uint256 contractId, bytes calldata data) external returns (bytes4); +} +} +``` +## Rationale + +The framework/interface provides a structured approach to smart contract tokenization and metadata management, aiming to enhance the Ethereum ecosystem's composability and interaction with the client side, creating more efficiency in the application layer. By facilitating the assignment of unique identifiers and linking contracts with metadata, the framework improves smart contracts' discoverability and interoperability. The specification of a metadata schema and URI offers a flexible means to enrich contract utility and integration capabilities. + +## Backwards Compatibility + +this framework introduces functionalities that complement the existing Ethereum ecosystem without necessitating modifications to current smart contract implementations. + + +## Reference Implementation +The reference implementation provided demonstrate the application of tokenizing and giving identifiers that can be used in collections of assets, contract owners can participate in creating and managing tokenized smart contracts with unique identifiers and metadata. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "../lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; +import "../lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol"; +import "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol"; +import "../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol"; +import "./IERC7625.sol"; + +/** + * @title ERC7625 Smart Contract Identification and Management + * @dev Implements the IERC7625 interface to manage smart contracts with unique IDs. This contract provides + * functionality to create unique contract IDs, lock and unlock asset transfers, approve operators for transfers, + * and manage ownership and approvals of contract IDs. It's designed for managing assets and their ownership + * securely within a decentralized application. + */ +contract ERC7625 is IERC7625, ERC165, Ownable, ReentrancyGuard { + /** + * @dev Emitted upon withdrawal of funds, specifying the beneficiary and amount. + * @notice Mapping from contract ID to owner address + */ + mapping(uint256 => address) private _contractOwners; + + /// @notice Mapping from owner address to list of owned contract IDs + mapping(address => uint256[]) private _ownedContracts; + + /// @notice Mapping from contract ID to its lock status (true if locked) + mapping(uint256 => bool) private _contractLocks; + + /// @notice Mapping from contract ID to approved address for transfer + mapping(uint256 => address) private _contractApprovals; + + event ContractReceived( + address operator, + address from, + uint256 contractId, + bytes data + ); + + /// @notice Counter to generate unique contract IDs + uint256 private _currentContractId; + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() Ownable(msg.sender) {} + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(IERC165, ERC165) returns (bool) { + return + interfaceId == type(IERC7625).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev Internally locks the transfers and withdrawals of a specific contract ID, preventing any changes. + * Emits an {AssetsLocked} event indicating the contract is locked. + * + * Requirements: + * - The caller must be the owner of the contract ID. + * + * @param contractId The ID of the contract to lock. + */ + function _lockAssetTransfers(uint256 contractId) external onlyOwner { + require( + msg.sender == _contractOwners[contractId], + "ERC7625: Unauthorized" + ); + autoLockAssetTransfers(contractId); + } + + function autoLockAssetTransfers(uint256 contractId) internal { + require( + _contractOwners[contractId] == msg.sender, + "ERC7625: Unauthorized" + ); + _contractLocks[contractId] = true; + emit AssetsLocked(msg.sender, true); + } + + /** + * @notice Unlocks asset transfers for a specific contract. + * @dev Only callable by the owner. + * @param contractId The unique identifier of the contract to unlock. + */ + function _unlockAssetTransfers(uint256 contractId) external onlyOwner { + require(_contractLocks[contractId], "ERC7625: Contract is not locked"); + _contractLocks[contractId] = false; + emit AssetsLocked(owner(), false); + } + + /** + * @dev See {IERC7625-balanceOfContractId}. + */ + function balanceOfContractId( + address owner + ) public view override returns (uint256) { + return _ownedContracts[owner].length; + } + + /** + * @dev See {IERC7625-ownerOfContractId}. + */ + function ownerOfContractId( + uint256 contractId + ) public view override returns (address) { + return _contractOwners[contractId]; + } + + /** + * @notice Transfers a contract from one address to another with additional data. + * @dev Safely transfers the ownership of a given contract ID from one address to another address. + * + * Before the transfer, the contract must be locked, ensuring no changes can occur during the process. + * If the target address is a contract, it must implement `IERC7625Receiver` and return the + * correct magic value upon successful receipt of the contract. The `data` parameter allows the + * sender to pass arbitrary data to the receiver in the `onERC7625Received` call. + * After the transfer, ownership is updated, and the new owner has the ability to unlock the contract. + * + * @param from The current owner of the contract. + * @param to The address to transfer the contract to. Must implement `IERC7625Receiver` if it is a contract. + * @param contractId The ID of the contract to transfer. + * @param data Additional data with no specified format, sent to the receiver. + * + * require The caller must be the owner of the contract ID. + * require The contract ID must be locked for transfer. + * require `to` cannot be the zero address. + * require If `to` is a contract, it must support the `IERC7625Receiver` interface. + */ + function safeContractTransferFrom( + address from, + address to, + uint256 contractId, + bytes calldata data + ) public payable override nonReentrant { + require( + _contractOwners[contractId] == from, + "ERC7625: Caller is not owner" + ); + require( + _contractLocks[contractId], + "ERC7625: Contract is not locked for transfer" + ); + require(to != address(0), "ERC7625: Transfer to the zero address"); + + // Update ownership to the new owner + _contractOwners[contractId] = to; + + // If 'to' is a contract, try calling onERC7625Received + if (to.code.length > 0) { + require( + IERC7625Receiver(to).onERC7625Received( + msg.sender, + from, + contractId, + data + ) == IERC7625Receiver.onERC7625Received.selector, + "ERC7625: Transfer to non IERC7625Receiver implementer" + ); + } + + // Keep the contract locked, leaving it to the new owner to unlock + emit TransferContract(from, to, contractId); + } + + /** + * @dev See {IERC7625-approveOperatorToTransfer}. + */ + function approveOperatorToTransfer( + address approved, + uint256 contractId + ) public payable override { + require( + _contractOwners[contractId] == msg.sender, + "ERC7625: Caller is not owner" + ); + _contractApprovals[contractId] = approved; + autoLockAssetTransfers(contractId); // Lock the asset transfers upon approval + emit ApprovalForTransfer(msg.sender, approved, contractId); + } + + /** + * @notice Sets or revokes approval for an operator to manage all of the sender's contracts. + * @dev Caller must be the owner. Locks all assets when approved. + * @param operator The operator's address. + * @param approved Approval status. + */ + function setApprovalForAllContracts( + address operator, + bool approved + ) public onlyOwner { + // Note: Implementation would vary based on contract design. Placeholder logic provided. + emit ApprovalForTransferOfAll(msg.sender, operator, approved); + } + + /** + * @notice Gets the approved address for a specific contract. + * @param contractId The unique identifier of the contract. + * @return The address approved to manage the contract. + */ + function getApproved( + uint256 contractId + ) public view override returns (address) { + return _contractApprovals[contractId]; + } + + /** + * @notice Withdraws funds from the contract. + * @dev Only callable by the owner. Uses ReentrancyGuard to prevent reentrancy attacks. + * @param to The recipient address. + * @param amount The amount to withdraw. + */ + + function withdraw( + address to, + uint256 amount + ) public onlyOwner nonReentrant { + require( + address(this).balance >= amount, + "ERC7625: Insufficient balance" + ); + payable(to).transfer(amount); + emit Withdraw(to, amount); + } + + function createContract() public onlyOwner returns (uint256) { + _currentContractId++; + _contractOwners[_currentContractId] = msg.sender; + _ownedContracts[msg.sender].push(_currentContractId); + emit TransferContract(address(0), msg.sender, _currentContractId); + return _currentContractId; + } + + /** + * @dev Handles the receipt of an incoming contract. This function is called whenever the contract ID is transferred + * to this contract via `safeContractTransferFrom`. It can be used to enforce custom logic upon receiving the contract, + * such as verifying the transfer, updating internal state, or locking the transfer of the contract ID until further + * action is taken. + * + * @param operator The address which initiated the transfer (typically the current owner). + * @param from The address from which the contract ID was transferred. + * @param contractId The ID of the contract being transferred. + * @param data Additional data sent with the transfer. + * @return bytes4 Magic value to signify the successful receipt of a contract ID. + */ + function onContractReceived( + address operator, + address from, + uint256 contractId, + bytes calldata data + ) external override returns (bytes4) { + // Example validation or action. Real implementation would depend on specific requirements. + // Verify that the contractId is expected or conforms to certain criteria + require( + _validateContractId(contractId), + "ERC7625: Unexpected contract ID" + ); + + // Update internal state to reflect the receipt of the contractId + _contractOwners[contractId] = address(this); // Transfer ownership to this contract + _ownedContracts[address(this)].push(contractId); // Record the contract as owned by this contract + + // Optionally, lock the contractId to prevent further transfers until explicitly unlocked + _contractLocks[contractId] = true; + + emit ContractReceived(operator, from, contractId, data); + + return this.onContractReceived.selector; + } + + /** + * @dev Validates that a contract ID is both owned and currently locked, indicating it's prepared for a secure transfer. + * This ensures the integrity and controlled management of contract transfers. + * + * @param contractId The ID of the contract being validated. + * @return bool indicating whether the contractId is both owned and locked, ready for transfer. + */ + function _validateContractId( + uint256 contractId + ) internal view returns (bool) { + // Check that the contract ID is owned, indicating it's not a new or unassigned ID. + bool isOwned = _contractOwners[contractId] != address(0); + + // Check that the contract ID is currently locked, indicating it's in a secure state for transfer. + bool isLocked = _contractLocks[contractId]; + + // The contract ID is valid for transfer if it's both owned and locked. + return isOwned && isLocked; + } +} +``` + + + +## Security Considerations + +When designing and implementing a smart contract identification and management framework such as proposed, several security considerations must be taken into account to ensure the robustness and reliability of the system. These considerations are vital not only for safeguarding the assets and metadata linked to the contractIds but also for maintaining the integrity and trustworthiness of the decentralized ecosystem it aims to support. + +#### Validation of ContractIds +- **Unique Identifier Integrity**: Ensuring the uniqueness and integrity of contractIds is paramount. Collisions or duplications in identifiers can lead to asset mismanagement or ownership disputes. Implementing a secure, collision-resistant hashing algorithm for generating contractIds can mitigate this risk. +- **Ownership Verification**: Before any operation that alters the state or ownership of a contract (such as transfers or locking/unlocking), the framework must rigorously verify the caller's authority over the contractId in question. This prevents unauthorized actions on contracts. + +#### Handling of `data` in Transfers +- **Data Validation**: When `data` is included in contract transfers, it's crucial to validate this input to prevent injection attacks or malformed data from disrupting contract logic. This can include checks on data size, format, or content based on the application's requirements. +- **Receiver Contract Validation**: The implementation assumes that the receiving address of a contract transfer will correctly handle the `data` payload if it's a contract implementing the interface. It's recommended to include mechanisms to verify the receiver's capability to handle the transfer safely, perhaps by implementing a try-catch mechanism around the call to the contract to handle any unexpected failures gracefully. + +#### Locking Mechanism +**Reentrancy Guards**: The locking and unlocking functionality must be protected against reentrancy attacks. While the provided implementation uses `ReentrancyGuard`, ensuring all public and external functions that modify contract states adhere to this protection is essential. +**Lock Bypass Risks**: There should be checks to prevent scenarios where a contract can be transferred or interacted with while it's supposedly "locked." This might involve a thorough review of state-changing functions to ensure they respect the lock status of contractIds. + +#### Contract Creation and Metadata Management +**Factory Contract Security**: The mechanism for creating new contractIds and associating metadata must be secure against attacks aiming to exploit contract creation for malicious purposes (e.g., creating an excessive number of contracts to spam the system). Rate limiting or permissions around contract creation could be considered. +**Metadata Integrity**: If metadata associated with contractIds is stored off-chain (e.g., using IPFS or another decentralized storage solution), ensuring the integrity and availability of this data is critical. Methods for verifying metadata authenticity and redundancy for data availability should be explored. + + +**Upgradeability Concerns**: If the system is designed to be upgradeable (using proxies or similar patterns), careful attention must be paid to the governance model for upgrades and the potential for upgrade functions to introduce vulnerabilities. + +By addressing these security considerations and remaining vigilant about potential vulnerabilities, the proposed smart contract identification and management framework can achieve a high level of security and reliability, fostering trust and adoption within the decentralized ecosystem. Open discussions and continuous improvement through community feedback and collaboration are encouraged to enhance the framework's robustness further. + +## Copyright + +Copyright and related rights waived via CC0.