How to create a Beacon Proxy

Hariharan Iyappan
Coinmonks
8 min readSep 12, 2022

--

Beacon proxy is a proxy pattern in which multiple proxies refer to a single smart contract to give them the address of the implementation contract. The contract which gives the implementation contract address to the proxies is called a beacon contract.

For a more detailed explanation on what is a proxy and why should you use it checkout this earlier article.

A beacon proxy is used when you have multiple proxies referring to a single implementation contract which is upgraded as we go along. If you were to use a Transparent proxy or UUPS Proxy you would have to upgrade the implementation contract address in all the proxies one by one. If your project needs multiple proxies referring to the same implementation then beacon proxy is good option to go with.

When we try to upgrade our smart contract deployed using the beacon pattern we make a call to the beacon contract and upgrade the implementation contract address stored there. Only the owner address will be able to make this upgrade which by default is the address that deployed the beacon. Once this beacon is updated all proxies referring to this beacon will start referring to the new contract and proxies will be upgraded to the new implementation at once.

Let’s dive in and deploy a smart contract with the UUPS proxy pattern.

Project Set Up

We will be using Hardhat for our Ethereum development workflow. I am using WSL2 in a windows machine for this walkthrough. You can adjust the steps slightly in case you are using a different set up. Let’s get started!

Run the following commands to create a directory and initialize it as a node project.

Install hardhat in this directory.

Initialize a hardhat type script project in the directory. Run the below command and choose “Create a TypeScript project” option. Go with the default parameters for rest of the prompts.

Install the Open zeppelin hardhat upgrades plugin which we’re going to use to easily deploy our proxy.

Also install the upgradeable contracts from Open zeppelin which is the upgradeable counterpart of Open zeppelin contracts.

Copy and paste the following into the hardhat.config.ts file. This will do the basic hardhat set up we require for this project.

Implementation contract to deploy with Beacon Proxy

Inside contracts folder create a folder for the implementation contract for which we’re going to deploy a beacon proxy.

In the contracts directory create a file named VersionAware.sol and copy paste the following code in it.

Here we create a basic skeleton of our implementation contracts so that we can easily see the version upgrades visibly after deployment and upgrade.

In the contracts/beacon directory create two files named BeaconProxyPatternV1.sol and BeaconProxyPatternV2.sol and copy paste the following code into it.

BeaconProxyPatternV1.sol

BeaconProxyPatternV2.sol

Both our contracts are simple initializable contracts which we will deploy using beacon proxy pattern now.

Here I attempt to explain a few advanced details of the the above contracts. Consider skipping it now and coming back later if you have difficulty understanding this on first read. Typical upgradeable contracts should not have a constructor because the constructor of the implementation contract can never be run in the context of the proxy contract. We have added a constructor here which is safe as it does not set to any of the storage variables. Leaving a contract without initializing it can pose a security threat. Calling the _disableInitializers method in the constructor makes the implementation contract not initializable which is much safer than leaving the implementation without a constructor and not initialized. Note that I have used initializer modifier in the initialize method in V1. This modifier makes sure that this initialize method is called only once, like solidity ensures for constructors. Also note that in V2 the modifier of the initialize method is reinitializer(2). Here 2 represents the version of the implementation contract. The reinitializer modifier has to be used instead of initializer because the proxy contract has already been initialized once in V1. There are more things and details to know about Initializer.sol contract which all upgradeable smart contracts should inherit. I will write more detailed article on it in future.

Now, run the following command to compile the smart contracts.

Beacon Proxy Deployment and Upgrade

First we will deploy version 1 with Beacon proxy and then upgrade it to version 2. We will use a hardhat script to do this. Create a script named beacon.js in scripts folder and copy paste the following code in it.

We first deploy the beacon contract with the deployBeacon method. Then we deploy two beacon proxies with the deployBeaconProxy method. Both proxies refer to the same beacon which in turn refers to the implementation contract. Then we upgrade the beacon contract with the upgradeBeacon method. Once version 1 is deployed with proxies we call the getContractNameWithVersion function on the proxies. This method will return a string according to what we made this method return in version 1 of the contract. After the upgrade is over we call the getContractNameWithVersion function again to see the change in the string returned. Let’s run the script and see the results. Use the following command to run the script.

You should see the following printed on your console.

Yay! the implementation got upgraded in both the proxies as soon as we upgraded the beacon. Which we can verify by seeing the strings returned by the getContractNameWithVersion function before and after the upgrade.

I would like to bring your attention to something in the results now. Note that even after the upgrade the value of the versionAwareContractName variable in the storage has not changed. This is because when we upgrade the implementation we only change the code of the smart contract, the storage remain intact. This is desirable because it is not straight forward to migrate storage of one smart contract to another. The storage of the proxy contract is used for storing the variables declared in the implementation smart contract(If you’re not clear why this is the case read this article where I explain this). Due to this even after upgrade the value of the versionAwareContractName storage variable remains the same as version 1. After we call the initialize method on the proxy 1 we can see in the next line that the value of versionAwareContractName has changed. It is in the initialize method that we set the value to that of version 2. Also, note that in the last line the value of versionAwareContractName in proxy 2 is still same as that of version 1. This is becaue we called the initialize method only on proxy 1 and not proxy 2.

Its important to understand what we have demonstrated in the previous section. Even though all proxies in beacon proxy pattern refer to the beacon for implementation contract address and only upgrading the beacon is enough to upgrade all proxies, the initializers of the subsequent versions should be called one by one on each of the proxy contracts. Otherwise the effect of the initialize method cannot be seen in the proxies where it was not called. (This is for the current implementation of Openzeppelin contracts and their upgrades plugin, it is not an ideal developer experience. I hope a better solution emerges in future so we don’t need to worry about this.)

Thanks for reading.

Who am I?

I am a full stack blockchain developer, passionate about building a decentralized and potentially more inclusive future. Have a blockchain development need?

Get in touch: 📧 hariharan@alumni.iitm.ac.in

Github

Join Coinmonks Telegram Channel and Youtube Channel learn about crypto trading and investing

Also, Read

--

--

Hariharan Iyappan
Coinmonks

Passionate techie | IITM 2020 | Software Engineer @PhonePe