Upgradeable and Clonable Blockchain Smart Contracts?! Proxies and Factory Patterns — Developers Guide

Harsh Winder
8 min readJun 6, 2023
Proxy Pattern Upgraded Graph

Motivation

As always, working in a new space such as developing blockchain infrastructure and applications, I always learn something new and I am surprised at how little information there is online regarding current standards for someone needing to quickly learn.

So to use my own learnings, experiences and contributing to the Python Brownie Web3 Module, I have decided to write out some code along with some explanation and comparisons, so anyone can more quickly get started with deploying proxies of varying patterns.

This is meant to be a guide for someone to quickly pick up the relevant concepts and methods to implement them. It is not a production ready or optimized code base.

Github Code

I’ve written a simple library making use of 3 patterns that are discussed below. I’ve kept it simple and shown examples of deployed contracts. Feel free to leave comments and/or discussions!

https://github.com/Harsh-Gill/brownie-proxy-factory-mix/tree/main

Important Concepts to know

  • Delegate Call

The Delegate Call is defined at (https://solidity-by-example.org/delegatecall/). It is a very powerful function that allows us to have proxies. Essentially we can have a contract A that uses contract B’s code to run in it’s own context.

This means that now we can have upgradeable contracts as we can run the contract A’s contract logic from Contract B or C or D … and so on!

  • Initialize Function (NO CONSTRUCTOR CONTRACTS)

When running a proxy pattern, constructors are not of much use. As constructors information is not officially stored on chain. Meaning a contract A that needs a constructor from contract B will not be able to get this information. A simple fix to this is that we define a “constructor-like” function and call it initialize() . Simply, this function is defined as an “intializer”, doing the same job a constructor would but actually being a function.

  • Storage Layout

Storage Layout is about how Solidity Smart Contracts store their variables. One big complexity and danger of DelegateCall pattern is that if contract A executes the code of Contract B, it needs to have the same or similiar variable storage structure so as to run the function correctly and not mix up the variables. This is no easy task and requires careful planning. If contract A and B store different variables at the same location, it may clash and cause unintended consequences.

Understanding Clonable Contracts

Clonable Contracts fundamentally are much simpler to understand and build a better foundation to understanding Proxies. Thus, let’s cover that first.

A factory contract is a pattern, where we deploy 1 main logic contract, termed the Implementation and upon which we can deploy countless “Clones” of this Implementation with unique properties such as a Unique Name or any other definable Paramter.

A graphical example is shown below :

So, an initial Implementation is deployed, upon which a factory can deploy numerous shell contracts that take upon the logic of the implementation.

This can significantly cut down the costs of deploying contracts, since the logic contract (which is the most expensive part), is deployed only ONCE and cheap clones that only contain information such as name can be deployed countless times.

It is a common standard in today’s Blockchain world. For example, the Uniswap Pools uses this exact Factory pattern to have countless pools that are all unique but rely on a single logic. For which, they also rely on a Factory Proxy pattern.

A Factory Contract does not have to be upgradeable, although it could be defined to be so, which we will look at later on in the section about Beacon Proxies.

Understanding Upgradeable Contracts

Thinking of upgradeable smart contracts might sound surprising, since the blockchain is often touted as an immutable (unchanging) system. This is still true but with some new logic we can make the deployed contracts become upgradeable while still being immutable.

Smart Contract Code by itself IS NOT upgradeable. However, using a proxy pattern allows us to acheive upgradeability.

This proxy pattern is a genius and simple solution. Essentially, we deploy 2 contracts instead of one.

We deploy a contract called a Proxy and one called an Implementation. The proxy is what users interact with and the implementation is where the proxy gets its logic. A graphical view would be like:

Proxy Pattern Graph

So for a user to access the functions A or B, it would have to call it through the Proxy. This is where the amazing upgradeability can be added in. If we wanted to add a new function C, we could create a new contract and simply change the pointer of our Proxy to the new implementation! This is shown below:

Proxy Pattern Upgraded Graph

Thus, we have not actually upgraded any code but rather we updated a pointer to get code from a new upgraded location!

This pattern can be implemented in numerous ways, and mainly we will be exploring the following proxy patterns :

- Transparent Upgradeable Proxy

This is one of the simplest patterns. It uses generally uses 3 contracts. These contracts are the Admin ( optional to include), Proxy, Implementation.

The Admin contract is used to handle all updates of the Proxy to a new implementation. It uses the contract defined by OpenZeppelin : https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/transparent/ProxyAdmin.sol

The Proxy and Implementation are clear at this point.

Pros

This is perhaps the simplest and cleanest Proxy design, which is a big advantage on it’s own. Additionally, it is relatively safer due to the presence of the Admin Contract (however that may not always be true as the admin itself could be a vector of attack). The additional inclusion of the Admin contract was to ensure safety and avoid any non-admin accounts potentially finding a vulnerability and being able to update the contract.

Cons

The Proxy contract will always DelegateCall if the from address is not the Admin Contract, which in essence avoids any non-Admin address contract to call the admin level functions. However this comes at the cost of a higher gas consumption, since EVERY CALL will first be checked to see whether it is from the Admin or not, which can stack up over time since this action requires reading from storage and thus increases gas.

- UUPS Proxy

The UUPS Proxy pattern was a solution made to decrease gas costs and also shift the upgradeability into the implementation logic itself. The UUPS Proxy does not use the pattern where each user has to first be checked to see whether it is from the admin or not. Which in the current blockchain eco-system , where gas fees are very costly, is a great advantage. Additionally, the added capability is that we can upgrade the contract and once we are satisfied, we can actually make the contract itself UNUPGRADEABLE at some chosen point.

Pros

The UUPS pattern can then be seen as a potentially cheaper and more powerful alternative. Additionally, we can choose for subsequent upgrades to actually become non-upgradeable. Which may be desirable for 0 trust or any specified reason.

Cons

However, it comes with more complexity and dangers since you must be careful in managing the pattern, as the upgradeability can be damaged if so (permanently). Also, when using UUPS, we will need to change our Logic Contract itself to inherit from it. Meaning that we will need some level of re-writing to integrate a contract to become UUPS compatible.

- Beacon Proxy

The Beacon Proxy Pattern is more complicated and requires deeper understanding of Solidity and DApp Infrastructures.

Essentially, it consists of a Factory Contract, Clones, Beacon Proxy and Implementation.

The Factory is able to deploy light clones that rely on the beacon Proxy to get the logic.

Pros

This pattern is great and vastly superior when deploying contracts that all rely on a similiar logic but have some unqiue parameters. It can cut the cost down significantly since we only deploy one implementation and beacon proxy, after which we can constantly deploy low gas cost proxies that can get the logic from the implementation.

Cons

This pattern requires a strong understanding of numerous components, ownership inheritances and also it is relatively rigid, meaning it is hard to change once it is in place and all clones must rely on a similiar pattern. However, it is also possible to make all contracts upgradeable (including Factory and Beacon). Though again, this is at the cost of complexity and potential problems/hacks.

Python Code (Web3 Brownie)

I have written a library implementing these patterns.

Mainly, there is a contract called SimpleContract

It has the following functions

  • initialize
  • getValue

Then this contract will be updated to SimpleContractV2

  • initialize
  • getValue
  • setValue (new upgraded function)

The Github code is based on Brownie.

The contracts and scripts have 3 main sub folders documenting the deployment of the pattern and usage of each.

To run the in a Transparent Proxy Pattern :

brownie run scripts/transparent_upgradeable_proxy/deploy.py

To run the UUPS Pattern :

brownie run scripts/uups_proxy/deploy.py

To run the Factory Beacon proxy :

brownie run scripts/beacon_proxy/deploy.py

Comparing Gas and Function Costs (Very brief!)

This is a very surface level comparison between the different patterns. In reality, the application itself could heavily influence these numbers and there are numerous other factors to consider— so do take these comparisons with a grain of salt. (If there is strong interest i can do an in-depth review of these patterns and their gas costs). However, a simple summary is below:

Simple Contract Call Cost

Deploy Simple Contract => ~ 94635 Gas

  • setValue => 22542 gas

Transparent Proxy Pattern

Additional Gas Costs :

Admin Contract => ~ 441875 Gas

Proxy Contract => ~ 628 329 Gas

Extra Overhead call :

  • setValue => (~ + 6500)

UUPS Proxy Pattern

Additional Gas Costs :

Proxy Contract => ~ 270 827 Gas

Implementation Contract => 603 785 Gas

Extra Overhead call :

  • setValue => (~ + 5500)

Beacon Factory Proxy Pattern

Additional Gas Costs

Factory Cost => 714 558 Gas

Beacon Cost => 287 611 Gas

Extra Overhead call:

  • setValue => + 1708 from upgradeable beacon

--

--

Harsh Winder

I am a physicist by academic training and I currently work as a software developer dealing with blockchain, data and backend systems.