HomeEIPs
EIPsERC-6224
ERC-6224

Contracts Dependencies Registry

An interface for managing smart contracts with their dependencies.
StagnantStandards Track: ERC
Created: 2022-12-27
Requires: EIP-1967, EIP-5750
Artem Chystiakov (@arvolear)
DiscussionsOriginal linkEdit
1 min read
Anyone may contribute to propose contents.
Go propose
Video
Anyone may contribute to propose contents.
Go propose
Original

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:

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:

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 and related rights waived via CC0.

Further reading
Anyone may contribute to propose contents.
Go propose
Adopted by projects
Anyone may contribute to propose contents.
Go propose

Not miss a beat of EIPs' update?

Subscribe EIPs Fun to receive the latest updates of EIPs Good for Buidlers to follow up.

View all
Serve Ethereum Builders, Scale the Community.
Resources
GitHub
Supported by