ERC-6224: Contracts Dependencies Registry
A registry for managing smart contracts with their dependencies.
Abstract
This EIP introduces an on-chain registry system that a decentralized protocol may use to manage its smart contracts.
The proposed system consists of two components: ContractsRegistry
and Dependant
. The ContractsRegistry
contract stores references to every smart contract used within a protocol, optionally making them upgradeable by deploying self-managed proxies on top, and acts as a hub the Dependant
contracts query to fetch their required dependencies from.
Motivation
In the ever-growing Ethereum ecosystem, 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 architectural facade; which are designed with forward compatibility in mind; which dependencies are transparent and clean are easier to develop and maintain. The given EIP tries to solve the aforementioned problems by presenting two smart contracts: the ContractsRegistry
and the Dependant
.
The advantages of using the provided system might be:
- Structured smart contracts management via specialized contracts.
- Ad-hoc upgradeability provision of a protocol.
- Runtime addition, removal, and substitution of smart contracts.
- Dependency injection mechanism to keep smart contracts' dependencies under control.
- Ability to specify custom access control rules to maintain the protocol.
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.
Overview
The system consists of two smart contracts:
ContractsRegistry
that is a singleton registry to manage and upgrade a protocol's smart contracts.Dependant
that is a mix-in which enables a dependency injection mechanism.
The following diagram depicts the relationship between the registry and its dependants:
ContractsRegistry
The ContractsRegistry
is the main contract of the proposed system. It MUST store the references to every standalone contract used within a protocol. The ContractRegistry
MAY be configured to deploy a proxy contract of choice on top of the registered contracts.
Additionally, the ContractsRegistry
MUST reject the registration of zero addresses.
The ContractsRegistry
MUST implement the following interface:
Dependant
The ContractsRegistry
works together with the Dependant
contract. Every standalone contract of a protocol MUST inherit Dependant
in order to support the dependency injection mechanism.
The required dependencies MUST be set in the overridden setDependencies
method, not in the constructor
or initializer
methods.
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 Dependant
contract MUST implement the following interface:
- The
Dependant
contract MAY store the dependency injector (usuallyContractsRegistry
) address in the special slot0x3d1f25f1ac447e55e7fec744471c4dab1c6a2b6ffb897825f9ea3d2e8c9be583
(obtained asbytes32(uint256(keccak256("eip6224.dependant.slot")) - 1)
).
Rationale
There are a few design decisions that have to be explicitly specified:
ContractsRegistry Rationale
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 of a protocol, it MAY be typical for the users to interact with it via block explorers or DAOs. Clarity was prioritized over gas usage.
Due to the string
identifier, the event parameters are not indexed. The string indexed
parameter will become the keccak256
hash of the contract name if it is larger than 32 bytes. This fact reduces readability, which was prioritized.
Reverts
The getContract
view function reverts if the requested contract is address(0)
. This is essential to minimize the risks of misinitialization of a protocol. Correct contracts SHOULD be added to the registry prior to any dependency injection actions.
The addContract
, addProxyContract
, addProxyContractAndCall
, and justAddProxyContract
methods revert if the provided address is address(0)
for the same risk minimization reason.
Dependant Rationale
Dependencies
The data
parameter is provided to carry additional application-specific context. It MAY be used to extend the method's behavior.
Injector
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.
Reference Implementation
Note that the reference implementation depends on OpenZeppelin contracts
4.9.2
.
ContractsRegistry Implementation
Dependant Implementation
Security Considerations
It is crucial for the owner of ContractsRegistry
to keep their keys in a safe place. The loss/leakage of credentials to the ContractsRegistry
will lead to the application's point of no return. The ContractRegistry
is a cornerstone of a protocol, access must be granted to the trusted parties only.
ContractsRegistry Security
- 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
- The
Dependant
contract MUST set its dependency injector no later than the first call to thesetDependencies
function is made. That being said, it is possible to front-run the first dependency injection.
Copyright
Copyright and related rights waived via CC0.