Proxy Basics
Warning: this research assumes you are familiar with how delegatecall works and how the logic of the callee contract applies the caller's context. If you are unfamiliar with how delegatecall works, consult in-depth explanations such as this one, this one, or this one.
Why do I need a proxy and how do I use it?
By design, contract code on a blockchain is immutable. Though a key feature, it leads to difficulty when considering upgradeability. Newer entrants may wonder why "upgrading" on a blockchain is necessary. Inevitabilities requiring code changes still remain, including: bug fixes, patches, optimizations, feature releases, etc.
The Proxy or Proxy Delegate pattern allows for this upgradeability. Put simply, there are two main approaches for proxies:
- A lightweight, entry-point caller contract
Ato use the logic of a callee contractBthroughdelegatecall:
Astores the address ofBin a state variable that can be changed.- If an upgrade is desired, a new callee contract
Cis deployed at a different address. Aupdates the state variable to point to the new callee contract,C.
- A factory contract
Athat creates a logic contractBwith opcodeCREATE2:
Bcontains aselfdestructto allow the contract to be destroyed.- Note: This is normally protected so only an admin or authorized account can call it.
- If an upgrade is desired,
Bis destroyed, and the factory can useCREATE2to deploy a new contract at the same address asB.
Additional reading for the proxy pattern can be found here, and here.
Can smart contracts be upgraded without proxies?
Yes, contract logic can be designed in such a way to allow a protocol to be upgraded without using delegatecall or CREATE2. An alternative approach is called the "data separation" pattern in this Trail of Bits blog post and another is contract migration.
Data Separation Pattern
This pattern works by using a primary contract A to serve as the entry-point, and doesn't contain complex logic besides calling external contracts. Calls to external contracts only use call, not delegatecall.
Addresses of the external contracts are stored in state variables that have restrictive setter functions, allowing only the owner to ever mutate their state. This allows A to update the state variables to point to new contract addresses whenever an upgrade is desired.
One example of this "data separation pattern" reviewed by yAudit previously is Tribe Turbo.