Build an upgradable protocol of any size with Synthetix’s Router plug-in
There is a limit to the size of smart contracts deployed on EVM blockchains. This can create complications during the development of protocols, where engineers may want an arbitrary amount of code to be executable at a single address.
To avoid the need to manage complex inheritance and dependency structures, Cannon includes Synthetix’s Router plug-in. This can be used by defining a router
operation in Cannonfiles. This accepts an array of contracts and automatically generates a router contract which will delegate calls to them. For a more technical explanation of the router, review its README.
In this guide, we’ll walk through a simple example that uses the router and adds a transparent upgradable proxy.
Start by installing/upgrading Cannon:
npm i -g @usecannon/cli
Then set up a new Foundry project:
forge init
This project will include a Counter.sol
contract by default. Duplicate this contract, rename it, and alter the function names in it. For this example, we’ll assume you’ve renamed the file and contract to AnotherCounter
.
Create cannonfile.toml
that deploys the two contracts and a router:
Build the Cannonfile:
cannon build
Run it. (By default, Cannon runs packages from the package manager. Here, we add the --registry-priority local
option to ensure we’re using the version of this package that you just built, regardless of what others have published.)
cannon sample-router-project --registry-priority local
Press i
to interact with the contracts in this project. You’ll see that the router contract exposes the functions from both contracts.
We can also deploy a transparent upgradeable proxy pointing at the router, making this protocol upgradeable. In the Cannonfile, add a setting for the admin (which will be allowed to upgrade the proxy) and then provision the package which includes the proxy contract:
If you alter one of your contracts, when building, Cannon will automatically detect this, generate a new router, and upgrade the proxy to point at it. (Old versions of the contracts aren’t included in the router, saving gas.) When building an upgrade, increase the version in your Cannonfile and use the --upgrade-from
option to reference the package from your previous version.
Changing the storage layout in smart contracts can irreversibly corrupt protocol data. Thoroughly understand how to avoid storage collisions when upgrading. If you’re using Hardhat, check out the hardhat-storage plug-in, which validates storage changes.
You can use libraries for executing storage reads/writes to create an MVC-style architecture. See the Synthetix V3 documentation for inspiration.
If the protocol is owned by a Safe, you can use the deployer to run upgrades. (Safe Modules and Safe Guards can be developed for additional on-chain, governance-related logic.) When your protocol no longer needs to be upgraded, it can be made immutable with a call to renounceOwnership
on the proxy.