Creating Upgradable Solidity Contract With Hardhat
Tags: openzepplin, upgradables, solidity, hardhat
Published: 2022-04-05 13:43:06

Smart contracts are immutable meaning once deployed on the blockchain, they can no longer be edited. This is a good thing but what if the deployed contract is buggy or you need to add a specific function to an already deployed smart contract without deploying a new contract thereby losing the state of the previous deployed contract. 

OpenZeppelin comes to the rescue by providing upgradables that allow smart contract to be updated without state loss. This tutorial makes use of Hardhat and OpenZeppelin upgradable contract. 

Create a new npm project by opening your terminal and typing: ( this tutorial assumes the user already has Node and npm installed in the system )

Tutorial Code lives [here](https://github.com/jamiebones/upgradable_contracts_tutorial)


* [Project Dependencies](#dependencies)  
* [Project] (#createProject)
* [How Upgradable Contract Works ](#contractwork)
* [Verify Upgradable Contract](#verify)
* [Upgrading a smart contract] (#upgrade)
* [Things to know when working with Upgradable Contracts](#know)

## <a name="dependencies"></a>Project Dependencies
```js
npm init --y
```
Open the `package.json` file that was created when the above command was run and add the following dependencies code below to the `package.json` file

```json
  "devDependencies": {
    "@nomiclabs/hardhat-ethers": "^2.0.3",
    "@nomiclabs/hardhat-etherscan": "^2.1.8",
    "@openzeppelin/hardhat-upgrades": "^1.12.0",
    "ethers": "^5.5.2",
    "hardhat": "^2.8.0"
  },
  "dependencies": {
    "dotenv": "^16.0.0"
  }
```

Install the above dependencies by running `npm i`. This installs the dependencies into the project.   `@openzeppelin/hardhat-upgrades` provides functionality for creating and deploying upgradable contracts. `@nomiclabs/hardhat-etherscan` is used for verifying the contract using Etherscan. `@nomiclabs/hardhat-ethers` allows hardhat to work with ether.js. 


## <a name="project"></a> Project 
Create a new hardhat project by running in the terminal:
```js
 npx hardhat
```
This presents us options to select a project template. Select the first option which is `Create a sample project` and this creates a sample project with boiler plate code. 


Create a `.env` file in the project directory. This file will contain our environment variable. In this project, we will need values for the following environmental variables which are: 
`INFURA_API_KEY`
`PRI_KEY`
`ETHERSCAN_API_KEY`
`INFURA_API_KEY` : our API key we get from [Infura](https://infura.io).We will need this to connect to infura
`PRI_KEY`: the primary key of your account in Meta mask. This is used for signing a transaction
`ETHERSCAN_API_KEY` : your API key from Etherscan. This will be used for verifying a contract.

Open the `hardhat-config.js` file and configure it by adding the code below.


```js
require("@nomiclabs/hardhat-ethers");
require("@openzeppelin/hardhat-upgrades");
require("@nomiclabs/hardhat-etherscan");

require('dotenv').config();
module.exports = {
  solidity: "0.8.10",
  networks: {
    ropsten: {
      url: `https://ropsten.infura.io/v3/${process.env.INFURA_API_KEY}`,
      accounts: [process.env.PRI_KEY],
    },
    rinkeby: {
      url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`,
      accounts: [process.env.PRI_KEY]
    }
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY,
  },
};
```


Open the contract folder in the project and delete the `Greeter.sol` file. Create a new file called `CalculatorV1.sol`. This will contain the smart contract we will deploy to the `rinkeby` network.

Inside the file `CalculatorV1.sol` replace it with the following code below: 

```js
pragma solidity 0.8.10;

import "hardhat/console.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract CalculatorV1 is Initializable {
   uint public val;
   function initialize(uint256 _val ) external initializer{
        val = _val;
    }
    function add(uint a, uint b) public pure returns (uint) {
        return a + b;
    }
   function getVal() public view returns (uint) {
        return val;
    }
}
```

This smart contract is a simple contract of a calculator. The contract inherits from the `Initializable` contract which is an Openzeppelin contract. It ensures that the `initialize` function is called only once. An upgradable contract does not have a constructor so the `initialize` function acts as a constructor and it must be called only once. The `initializer` modifier ensures the function is called once.
 
The contract has a public variable named `val` and three functions which are `initialize`, `add` and `getVal`. We want to deploy this contract to the Rinkeby network which we have set up in the `hardhat-config.js` file. 

Create a new file inside the scripts folder and call it `deploy_contract.js`. This file will contain the code that will deploy our Calculator contract for us. 

Inside the `deploy_contract.js` file add the following code:

```js
//scripts/deploy_contract.js
const { ethers, upgrades } = require("hardhat");

async function main() {
   const CalculatorV1 = await ethers.getContractFactory("CalculatorV1");
    console.log("Deploying Calculator...");
    const calculator = await upgrades.deployProxy(CalculatorV1, [42], {
        initializer: "initialize",
    });
    await calculator.deployed();
    console.log("Calculator deployed to:", calculator.address);
}

main();
```

The code above requires `ethers` and `upgrades` from `hardhat`. An `async` function is created and inside the function, the contract factory is retrieved using `ethers` with the name of the contract ( CalculatorV1 ). The `upgrades.deployProxy` is used to deploy the contract passingin  the contract factory and the initialization function with its parameter passed.  

Remember in the contract, we have an `initialize` function that sets the value of the `val` variable. This function is called as the contract is being deployed passing in the value `42` as the parameter to the function. 

Run on the terminal the following code to deploy the contract:

```js
 npx hardhat run --network rinkeby scripts/deploy_contract.js
```

After some few second the contract is deployed with the 
contract address logged to the console.

## <a name="contractwork"></a> How Upgradable Contract Works

When we deployed the contract, three contracts were deployed in total. These are a `Proxy` contract, a `Proxy Admin` contract and the Implementation contract which is our `CalculatorV1`. When a user interacts with the contract, he is actually interacting with the `Proxy` contract. The `Proxy` contract makes a delegateCall to our `CalculatorV1` contract. For example A contract named `A` makes a delegateCall to a contract `B` calling a function in contract `B`. The function in `B` is executed in the state of variable `A`. 

For our upgradable contract, the `Proxy` contract calls the `Implementation` contract (CalculatorV1). The state change is made on the `Proxy` contract. The Proxy Admin contract is used for updating the address of the implementation contract
 inside the Proxy contract.


## <a name="verify"></a> Verify Upgradable Contract
When we deployed our contract, we got back the address of the Proxy contract. if we search for this address on Ether scan we are presented with a contract with name `
TransparentUpgradeableProxy`. This contract is the `Proxy` contract and this will be responsible for calling the Implementation contract. 

To verify the Implementation contract and publish the contract code we have to look into the project folder and you will see a folder named `.openZeppelin`. Open the folder and you will find a file named `rinkeby.json`. This file is so named because of the network we deployed the contract to. This file was auto generated by `hardhat` when we ran the deployed script. Inside this file the addressees of the Implementation contract, Proxy admin and the Proxy are kept. As the contract is updated the new addresses are added to the file. Copy the address of the
 Implementation contract and proceed to the terminal for verification.
Run this code at the terminal: 
`npx hardhat verify --network rinkeby contractAddress`

Replace contract address with the Implementation address that was copied and run the code. This verifies the contract source code. We also need to verify the Proxy admin contract. Go to Etherscan and search for the Proxy contract using its address. Click on the Contract tab then click on Code tab and click on the more options. Click on `is this a proxy?` and then click on verify. The Proxy contract will be verified. 


<a name="upgrade"></a> Upgrading a Contract

Create a new file inside the contract folder and name it `CalculatorV2`. 

```js
pragma solidity 0.8.10;

import "hardhat/console.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract CalculatorV2 is Initializable {
    uint public val;

    function add(uint a, uint b) public pure returns (uint) {
        return a + b;
    }

    function multiply(uint a, uint b) public pure returns (uint) {
        return a * b;
    }
  function getVal() public view returns (uint) {
        return val;
    }

}
```

We have added a new function to this version of the contract. The `multiply` function is added. To upgrade the deployed contract. Create a script inside the `scripts` folder and create a file `upgrade_contract.js`. Inside this file put the following code.

```js
const { ethers, upgrades } = require("hardhat");
//the address of the deployed proxy
const PROXY = "0xaf03a6F46Eea7386F3E5481a4756efC678a624e6";

async function main() {
    const CalculatorV2 = await ethers.getContractFactory("CalculatorV2");
    console.log("Upgrading Calculator...");
    await upgrades.upgradeProxy(PROXY, CalculatorV2);
    console.log("Calculator upgraded");
}

main();
```
The address of the implementation Proxy and the contract factory of the new version of the contract is passed as parameters to `upgrades.upgradeProxy`. Run the code by typing on the terminal :

```js
 npx hardhat run --network rinkeby scripts/upgrade_contract.js
```
 This will update the address of the Implementation contract in the Proxy contract to make use of the new version deployed. Run the `getVal` contract to retrieve the value of the state variable `val`. You will notice that the value of `val` is still the value we initiated it to be when we deployed the first version of the contract. That is the beauty of upgradable contracts which is the preservation of variable state.

 To verify the contract, we have to perform the same steps that was used to verify the first version of the contract.

##<a name="know"></a>Things to know when working with Upgradable Contracts
When working with Upgradable contracts the following points should be noted:

* Constructor: An upgradable contract can not have a `constructor`. If you have code that must run when the contract is created. The code should be placed in an init function that will get called when the contract is deployed. Openaeppelin `Initializable` can be used to ensure a function is called once. (`initializer`)

```js
function initialize(uint256 _val ) external initializer {
        val = _val;
}
```
The `initialize` function will be called only once because of the `initializer` modifier attached to it. 

* state variables : state variables in upgradable contracts once declared cannot be removed. Assuming we have a version one contract where we define the following state variables :

```js
uint public val;
string public name;
```

When deploying version two of the contract, we must ensure that version two of the contract upgrade also contain the same variable as version one in the same order as was defined in version one. The order of the variable matters. if we want to use new state variables, they are added at the bottom. 


```js
uint public val;
string public name;
string public newVariableOne;
uint public newVariableTwo;
```

* variable initialization : only state variable declared as `const` and `immutable` can be initialize. This is because initializing a state variable will attempt to create a storage for that variable. And as we know the Implementation contract don't use its state. The Proxy contract provides the storage used by the Implementation contract. 

The value of variables declared as `const` are placed in the application code of the contract instead of in storage. That's why only `const` variable can be initialize.

* Implementation contract can not contain code that will self destruct the contract. If a contract is self destruct and removed from the blockchain, the Proxy contract will no longer know where to look to execute functions.

```js
function kill() external {
     selfdestruct(payable(address(0)));
}
```

## Summary
Having a way to upgrade smart contracts could come in useful when you need to change and improve the contract code. Thanks for reading...