--- eip: 6224 title: Contracts Dependencies Registry description: An interface for managing smart contracts with their dependencies. author: Artem Chystiakov (@arvolear) discussions-to: https://ethereum-magicians.org/t/eip-6224-contracts-dependencies-registry/12316 status: Draft type: Standards Track category: ERC created: 2022-12-27 requires: 1967, 5750 --- ## Abstract The EIP standardizes the management of smart contracts within the decentralized application ecosystem. It enables protocols to become upgradeable and reduces their maintenance threshold. This EIP additionally introduces a smart contract dependency injection mechanism to audit dependency usage, to aid larger composite projects. ## Motivation In the ever-growing Ethereum, projects tend to become more and more complex. Modern protocols require portability and agility to satisfy customer needs by continuously delivering new features and staying on pace with the industry. However, the requirement is hard to achieve due to the immutable nature of blockchains and smart contracts. Moreover, the increased complexity and continuous delivery bring bugs and entangle the dependencies between the contracts, making systems less supportable. Applications that have a clear facade and transparency upon their dependencies are easier to develop and maintain. The given EIP tries to solve the aforementioned problems by presenting two concepts: the **contracts registry** and the **dependant**. The advantages of using the provided pattern might be: - Structured smart contracts management via specialized contract. - Ad-hoc upgradeability provision. - Runtime smart contracts addition, removal, and substitution. - Dependency injection mechanism to keep smart contracts' dependencies under control. ## 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. ### ContractsRegistry The `ContractsRegistry` MUST implement the following interface: ```solidity pragma solidity ^0.8.0; interface IContractsRegistry { /** * @notice REQUIRED The event that is emitted when the contract gets added to the registry * @param name the name of the contract * @param contractAddress the address of the added contract * @param isProxy whether the added contract is a proxy */ event AddedContract(string name, address contractAddress, bool isProxy); /** * @notice REQUIRED The event that is emitted when the contract get removed from the registry * @param name the name of the removed contract */ event RemovedContract(string name); /** * @notice REQUIRED The function that returns an associated contract by the name * @param name the name of the contract * @return the address of the contract */ function getContract(string memory name) external view returns (address); /** * @notice OPTIONAL The function that checks if a contract with a given name has been added * @param name the name of the contract * @return true if the contract is present in the registry */ function hasContract(string memory name) external view returns (bool); /** * @notice RECOMMENDED The function that returns the admin of the added proxy contracts * @return the proxy admin address */ function getProxyUpgrader() external view returns (address); /** * @notice RECOMMENDED The function that returns an implementation of the given proxy contract * @param name the name of the contract * @return the implementation address */ function getImplementation(string memory name) external view returns (address); /** * @notice REQUIRED The function that injects dependencies into the given contract. * MUST call the setDependencies() with address(this) and bytes("") as arguments on the substituted contract * @param name the name of the contract */ function injectDependencies(string memory name) external; /** * @notice REQUIRED The function that injects dependencies into the given contract with extra data. * MUST call the setDependencies() with address(this) and given data as arguments on the substituted contract * @param name the name of the contract * @param data the extra context data */ function injectDependenciesWithData( string calldata name, bytes calldata data ) external; /** * @notice REQUIRED The function that upgrades added proxy contract with a new implementation * @param name the name of the proxy contract * @param newImplementation the new implementation the proxy will be upgraded to * * It is the Owner's responsibility to ensure the compatibility between implementations */ function upgradeContract(string memory name, address newImplementation) external; /** * @notice RECOMMENDED The function that upgrades added proxy contract with a new implementation, providing data * @param name the name of the proxy contract * @param newImplementation the new implementation the proxy will be upgraded to * @param data the data that the new implementation will be called with. This can be an ABI encoded function call * * It is the Owner's responsibility to ensure the compatibility between implementations */ function upgradeContractAndCall( string memory name, address newImplementation, bytes memory data ) external; /** * @notice REQUIRED The function that adds pure (non-proxy) contracts to the ContractsRegistry. The contracts MAY either be * the ones the system does not have direct upgradeability control over or the ones that are not upgradeable by design * @param name the name to associate the contract with * @param contractAddress the address of the contract */ function addContract(string memory name, address contractAddress) external; /** * @notice REQUIRED The function that adds the contracts and deploys the Transaprent proxy above them. * It MAY be used to add contract that the ContractsRegistry has to be able to upgrade * @param name the name to associate the contract with * @param contractAddress the address of the implementation */ function addProxyContract(string memory name, address contractAddress) external; /** * @notice RECOMMENDED The function that adds an already deployed proxy to the ContractsRegistry. It MAY be used * when the system migrates to the new ContractRegistry. In that case, the new ProxyUpgrader MUST have the * credentials to upgrade the newly added proxies * @param name the name to associate the contract with * @param contractAddress the address of the proxy */ function justAddProxyContract(string memory name, address contractAddress) external; /** * @notice REQUIRED The function to remove contracts from the ContractsRegistry * @param name the associated name with the contract */ function removeContract(string memory name) external; } ``` - The `ContractsRegistry` MUST deploy the `ProxyUpgrader` contract in the constructor that MUST be set as an admin of `Transparent` proxies deployed via `addProxyContract` method. - It MUST NOT be possible to add the zero address to the `ContractsRegistry`. - The `ContractsRegistry` MUST use the `IDependant` interface in the `injectDependencies` and `injectDependenciesWithData` methods. ### Dependant The `Dependant` contract is the one that depends on other contracts present in the system. In order to support dependency injection mechanism, the dependant contract MUST implement the following interface: ```solidity pragma solidity ^0.8.0; interface IDependant { /** * @notice The function that is called from the ContractsRegistry (or factory) to inject dependencies. * @param contractsRegistry the registry to pull dependencies from * @param data the extra data that might provide additional application-specific context/behavior * * The Dependant MUST perform a dependency injector access check to this method */ function setDependencies(address contractsRegistry, bytes calldata data) external; /** * @notice The function that sets the new dependency injector. * @param injector the new dependency injector * * The Dependant MUST perform a dependency injector access check to this method */ function setInjector(address injector) external; /** * @notice The function that gets the current dependency injector * @return the current dependency injector */ function getInjector() external view returns (address); } ``` - The `Dependant` contract MUST pull its dependencies in the `setDependencies` method from the passed `contractsRegistry` address. - The `Dependant` contract MAY store the dependency injector address in the special slot `0x3d1f25f1ac447e55e7fec744471c4dab1c6a2b6ffb897825f9ea3d2e8c9be583` (obtained as `bytes32(uint256(keccak256("eip6224.dependant.slot")) - 1)`). ## Rationale There are a few design decisions that have to be specified explicitly: ### ContractsRegistry Rationale #### Usage The extensions of this EIP SHOULD add proper access control checks to the described non-view methods. The `getContract` and `getImplementation` methods MUST revert if the nonexistent contracts are queried. The `ContractsRegistry` MAY be set behind the proxy to enable runtime addition of custom methods. Applications MAY also leverage the pattern to develop custom tree-like `ContractsRegistry` data structures. #### Contracts identifier The `string` contracts identifier is chosen over the `uint256` and `bytes32` to maintain code readability and reduce the human-error chances when interacting with the `ContractsRegistry`. Being the topmost smart contract, it MAY be typical for the users to interact with it via block explorers or DAOs. Clarity was prioritized over gas usage. #### Proxy The `Transparent` proxy is chosen over the `UUPS` proxy to hand the upgradeability responsibility to the `ContractsRegistry` itself. The extensions of this EIP MAY use the proxy of their choice. ### Dependant Rationale #### Dependencies The required dependencies MUST be set in the overridden `setDependencies` method, not in the `constructor` or `initializer` methods. The `data` parameter is provided to carry additional application-specific context. It MAY be used to extend the method's behavior. #### Injector Only the injector MUST be able to call the `setDependencies` and `setInjector` methods. The initial injector will be a zero address, in that case, the call MUST NOT revert on access control checks. The `setInjector` function is made `external` to support the dependency injection mechanism for factory-made contracts. However, the method SHOULD be used with extra care. The injector address MAY be stored in the dedicated slot `0x3d1f25f1ac447e55e7fec744471c4dab1c6a2b6ffb897825f9ea3d2e8c9be583` to exclude the chances of storage collision. ## Reference Implementation *0xdistributedlab-solidity-library dev-modules* provides a reference implementation. ## Security Considerations The described EIP must be used with extra care as the loss/leakage of credentials to the `ContractsRegistry` leads to the application's point of no return. The `ContractRegistry` is a cornerstone of the protocol, access must be granted to the trusted parties only. ### ContractsRegistry Security Considerations - The non-view methods of `ContractsRegistry` contract MUST be overridden with proper access control checks. - The `ContractsRegistry` does not perform any upgradeability checks between the proxy upgrades. It is the user's responsibility to make sure that the new implementation is compatible with the old one. ### Dependant Security Considerations - The non-view methods of `Dependant` contract MUST be overridden with proper access control checks. Only the dependency injector MUST be able to call them. - The `Dependant` contract MUST set its dependency injector no later than the first call to the `setDependencies` function is made. That being said, it is possible to front-run the first dependency injection. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).