SIP-307: Router Proxy Architecture (V3)
|Network||Ethereum & Optimism|
|Implementor||Alejandro Santander (@Ethernaut), Leonardo Massazza (@leomassazza), Matias Lescano (@mjlescano)|
This SIP proposes the base architecture for the Synthetix v3 system, as well as all v3 related systems, such as governance instances (spartan-council, etc). This new architecture minimizes complexity during releases, provides a novel way of overcoming the EVM's smart contract size limits, and makes system upgrades very explicit to the community.
This SIP presents the core smart contract architecture for the v3 system. It also briefly touches on non-smart contract related architecture components, but the main focus of the SIP is to agree on the core component of the architecture; its proxy system.
The non-smart contract components of the architecture are presented briefly because they are important for the proper use of the smart contract architecture. These will be implemented via low level tooling, implemented as a series of hardhat plugins. These plugins will be generic, allowing any project to use the base architecture. The plugins will allow seamless deployment and validation of system upgrades, as well as command line interaction with deployed instances.
The existing v2 architecture has become outdated, and has accumulated too much technical debt. It makes it hard for developers to add new features to the protocol, and is prone to make mistakes. It also makes the release process complicated, and fragile. The new architecture was designed specifically to target these problems, and produce a foundation that easy to build upon, and easy to use during releases.
The proposed architecture is basically composed of a fundamental "deployer" plugin. This plugin will identify a system's modules and automatically determine when a module's source code has changed and needs to be re-deployed. Instead of connecting the system's modules via an AddressResolver, modules are "merged" into a single super-contract that uses a technical workaround to overcome the EVM's contract size limitations. This means that no connections have to be made for modules to be able to interact with each other, which is one of the most fragile areas of the v2 architecture.
The merging of the modules is done via a contract generated by tooling, known as the Router, which acts as the implementation of a single proxy for the entire system. The generated router, is simply a table that maps incoming selector calls to their corresponding module. This is an example of such Router code: https://optimistic.etherscan.io/address/0x6209043FcEf509ba5624054Da26Eb8d52a12efcc#code. As you can see, the router will be a very explicit manifestation of the current composition of the system.
The proposed architecture's ability to overcome smart contract size limits in a way that minimizes developer efforts provides an ideal way to engineer complex systems like Synthetix, which are very iterative and require frequent updates. This is what the v3 architecture optimizes for. The router proxy architecture is also efficient, since it uses modern
delegatecall (as opposed to v2's
call) based proxies, and constructs its routing table using Yul assembly language, resulting in a proxy system that is more efficient than other proxies such as OpenZeppelin's transparent proxies.
delegatecall method is chosen for proxies, since it makes coding much easier. However, misuse of this type of proxies can cause storage collisions. The proposed architecture includes extensive abstract syntax tree validations to ensure that storage collisions do not exist. What this means is that we remove complexity from the developer, and transfer it to the tooling.
Since the tooling is generic, and it can be used by other projects, we expect it to become more robust as it gains popularity in web3.
The proposed architecture is being developed here: https://github.com/Synthetixio/synthetix-v3/tree/main/packages
Next, we describe other characteristics of the architecture.
Universal Upgradeable Proxy Standard
The main system proxy is an UUPS proxy which is more gas efficient than transparent proxies by implementing the upgradeability functions in the implementation instead of the proxy. In this architecture, we develop a way of ensuring that all upgrades are further upgradeable via a special mechanism in the UpgradeModule.
Since the upgradeability code in UUPS proxies is in the implemetation instead of the proxy, every update needs to make sure that the new system is upgradeable. Our implementation of UUPS proxies includes an on-chain mechanism to ensure that all new implementations are not sterile. Even though this check is in place and is robust, governance may choose in the future to remove this "fertility" check mechanism, and thus eventually allow a finally update to the system that is no longer upgradeable.
Even though the entire system will be behind a single proxy, the system may connect to other systems via its AssociatedSystems module. These associated systems may be another contract of any kind, but also other router proxy systems. Associated systems behind a router proxy may be core components of the system when used as libraries (not in the Solidty library sense, but in the sense of commonly available code that multiple instance may use). An example associated system could be the SNX token, intended to be a simple ERC20 token with no extra code or functionality, fully compliant to the ERC20 standard. Another associated system could be a synths manager that would have the ability to update all synths in production in a single transaction.
Associated systems may be used simply as libraries, by
delegatecalling into them, or as independent systems such as a token.
We are developing a separate tool called Cannon to manage the interconnectivity of associated systems.
Since the router architecture uses
delegate call proxies, the execution context of the system will be the proxy itself. This means that all system state will be in a single contract, and modules will simply specify the system's code. This simplifies development significantly, and will allow for much faster and safer iteration of the protocol. All state is accessible to all modules immediately, without the need to perform calls between system components. No special treatment has to be given to the message sender of a transaction, nor the emission of events.
To avoid conflicts within this globally state contained in the main proxy, the system will use namespaces for each module. E.g.
io.synthetix.rates, etc. Such namespaces will be used both by developers for clarity, as by tooling for systematically checking for storage collisions. Namespaces can only be appended to, and cannot be reordered.
In a way, this architecture is similar to the Diamond Proxy pattern, but instead of relying on storage to define the system's composition at any time, it clearly maps it out in a hard coded table within a verified contract. Again, this allows anyone to very easily verify and understand the composition of the system at any given time. The routing table also uses a binary search algorithm that makes the routing of calls to their corresponding modules highly gas efficient.
The architecture is built on Solidity's latest 0.8.x versions.
The tooling performs a series of checks on systems based on this architecture, such as ensuring that storage collisions do not exist, modules are frozen when not initialized, upgrades cannot be bricked, etc.
Beacon proxies will allow modules that manage multiple ERC20 (E.g. synths) to upgrade all synths with a single transaction. In this sense, synths would also be associated systems, each having their own functions like
transfer, etc. The "beacon" is simply a contract that stores the current implementation code for all the synths. The module in charge of managing synths simply has to notify the beacon of an upgrade, and this upgrade will be propagated atomically to all synths.
This layout is prone to change, but is useful to illustrate the core of the architecture.
- deployer - hardhat plugin in charge of managing updates to systems based in this architecture
- cli - hardhat plugin that allows interaction with any deployed system and its associated systems on any chain via the command line interface
- core-contracts - generic contracts such as ERC20, ERC721, etc (replacement of OpenZeppelin)
- core-modules - module implementations common to multiple systems, such as OwnerModule, UpgradeModule, ElectionModule, etc
- synthetix-governance - Synthetix specific governance module implementations
- spartan-council - project based on this architecture that uses the ElectionModule
Copyright and related rights waived via CC0.