Every ERC Explained (Part 2): Contract Design Patterns
Covering contract deployments, factories, proxies, and upgrades.
Ethereum Improvement Proposals (EIPs) are design documents to standardize some feature for the Ethereum community. Some standards like EIP1559 and Proof of Stake change the EVM network itself and are "Core" EIPs. There are also application standards such as the ERC20 token that serve as smart contract implementation references - these are categorized as "ERC".
If you missed the explainer on all ERC tokens, check out the first article:
📊 Here’s the dashboard with all standards.
Contract Design Patterns
Tokens are pretty concrete and straightforward to understand. Contract management becomes much more low-level and abstract. There are three key concepts to learn:
Contract Ownership and Roles
Contract Deployment and Minimal Proxies
Upgradable Contracts (Upgrade, Beacon, Diamond)
Remember that you can use the dashboard to see deployment trends, read the ERC proposal, and find top example contracts across all EVM chains. These are the ERC standards we’ll cover today:
Contract Ownership and Roles
Contracts often times have functions that only allow certain addresses to call them. For example, USDT (an ERC20 token) has a “mint()” function that can only be called by the owner address (using a modifier)
This owner is set when the contract is deployed (in the constructor), though it can be transferred anytime to another address by the owner.
Ownership has evolved into the “Ownable.sol” and “Roles.sol” implementations, which allow for greater flexibility on functions.
I’m not actually sure why, but looks like ERC5313 implementations of the “owner()” function have been making a comeback over the last year. There are some implementations that use “admin” instead of owner - so detecting either ERC173 or ERC5313 is actually somewhat inconsistent.
Contract Deployment and Minimal Proxies
There are two ways of deploying a contract:
Directly deploying the bytecode, where there is no “to” address called in the transaction. The creation_traces table “from” column will also be empty.
Deploying the bytecode through a contract (factory). The “to” address will be the factory. Some factories are made to deploy the exact same contract, others serve purposes like CREATE2 deterministic deploying (predetermined address for a contract).
Either way, you have the option of decoupling storage from logic using a “proxy”. You have to deploy one contract (the implementation) to hold the “logic”, and then deploy one contract (the proxy) to hold the “storage”. On deploy, a reference to the implementation address is stored on the proxy. An example of this is the USDC token, where you call the USDC proxy to make a transfer and it “asks” the implementation contract what “transfer()” means. The token balances/changes are still stored on the proxy contract (this works because of DELEGATECALL).
You can see below that USDC exists at “0xa0b98699…” and the implementation is said to exist at “0xa2327a9…”
This implementation shows up in the invocation flow as well, if you track the traces of a USDC transfer:
Now, USDC is not a minimal proxy. A minimal proxy is the smallest/simplest version of a proxy contract, typically used in factories. This saves a lot of gas on each deployment because the bytecode you are deploying is much smaller, however adds a bit of gas to each transaction because of the extra DELEGATECALL.
Minimal proxies make up more than 90% of contracts deployed. They’ve been popular on Ethereum mainnet since May 2020, though the volume of deployments cross-chain really exploded in November 2022:
One of the most popular minimal proxies by deployments and transactions is Mirror Writing Editions, with over 8000 proxies deployed on Optimism.
There is an interesting “MetaProxy” version of the minimal proxy standard that seems to have never taken off.
Upgradable Contracts (Upgrade, Beacon, Diamond)
If you have a keen eye, you may have noticed that the USDC screenshot mentioned that an implementation was “previously recorded to be on 0xb7277a6…” Simply put - the implementation was upgraded.
It’s actually been upgraded twice already - and if you look at the last USDC v2 upgrade transaction you can see there is a lot happening. There are tons of risks and considerations to updating a live contract holding billions of dollars of value. While many people believe contracts should just be immutable so that you don’t have centralization risk, upgrades have played a key role in fixing contract bugs.
This standard for upgradable proxies is ERC1967 - you’ll likely find these contracts named “TransparentUpgradeableProxy” or “UUPSUpgradeable” (read more).
Outside of BNB and Polygon, deployments of this standard have slowed down since late 2022. I’m actually not too sure why that’s the case.
For ERC1967 proxies deployed on Ethereum, some of the top trending contracts include Layerzero, USDC, Zora, and Rabbithole. Notably, it looks like Rabbithole has been upgraded already 19 times.
Now, let’s say you have hundreds of proxies that share an implementation. You might not want to go and call upgrade on each one - instead, you would want to update them all in one call. In this pattern, each proxy holds the address to a beacon contract that holds the address of the shared implementation. This is still done using the ERC1967 standard.
Most of the top examples of this are bridge tokens right now. All of the ERC20s bridged from Ethereum likely use the same factory created token on the target chain (looks like mostly Polygon and Binance right now). In case of a security issue, it is great to be able to upgrade all of these at once.
Getting to the highest level of complexity, we have a single proxy with MULTIPLE implementations - one per function to be exact (called a facet). This is the most modular (and most complex) contract design I know of, and it’s called the Diamond standard (ERC2535).
All the other standards we’ve seen so far have millions of deployments. This standard has seen less than ten thousand deployments total.
Gelato and Aavegotchi are the ones from trending examples that I’ve heard of the most. Dune currently does not really decode Diamond proxy contracts due to their complexity, but if you want to learn to navigate them then check out Banteg’s example walkthrough.
If you want to go really deep on the technicals of upgrades and how they work, read this blog. Trust is a big component of upgrades, and some “time delayed” versions of the standard have been developed albeit not really adopted outside of governance timelocks.
📊 And again here’s the dashboard with all standards.
Now you know your ERCs, next time won’t you analyze with me
Dad jokes aside - If you understand tokens and contract management standards, then you are well prepared to dive into any protocol analysis.
There are more niche ERCs such as account abstraction (ERC4337/2771) and EIP712 signatures (ERC1271) - for these, you should read about the “modular stack” as they affect a more expansive design space:
As always, thanks for reading and be sure to subscribe (or refer a friend) if you found this article helpful!