Web3 Tutorial: write upgradeable smart contract (proxy) using OpenZeppelin
Tags: blockchain, web3, solidity, dapp
Published: 2022-02-21 10:04:00

There are several good articles about upgradeable smart contracts or proxy pattern. But I can't find a step-by-step how-to guide to build, deploy a proxy contract and interact with it. So I wrote one and here it is.

In this tutorial, I list 7 details tasks which you can follow to deploy proxy contract to local testnet as well as public testnet Ropsten. Each task has several sub-tasks. 

We will use [OpenZeppelin proxy contracts](https://docs.openzeppelin.com/contracts/4.x/api/proxy) and [OpenZeppelin Upgrades plugin for Hardhat](https://docs.openzeppelin.com/upgrades) (or Truffle) . 

Special thanks for two related guides in OpenZeppelin ecosystem: [OpenZeppelin Upgrades: Step by Step Tutorial for Hardhat and Gnosis Safe](https://forum.openzeppelin.com/t/openzeppelin-upgrades-step-by-step-tutorial-for-hardhat/3580) by abcoathup, OpenZeppelin Defender guide [Upgrading a contract via a multi-sig and Defender](https://docs.openzeppelin.com/defender/guide-upgrades).

---

## How upgradeable smart contract works? 

![how proxy contract works](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/84bmjpjdb8d7rbc4szqc.png)
 

This illustration explains how upgradeable smart contracts work. To be specific, this is transparent proxy pattern. Another one is UUPS proxy pattern (Universal Upgradeable Proxy Standard).

Upgradeable smart contracts are composed of 3 contracts:

- **Proxy contract.** The smart contract which user interacts with. It will keep data/state which means data is stored in the context of this proxy contract account. This is an [EIP1967 standard](https://eips.ethereum.org/EIPS/eip-1967) proxy contract.

- **Implementation contract.** The smart contract  provides functionality and logic. Please note that the data is also defined in this contract. This is the smart contract you are building.

- **ProxyAdmin contract.** The contract links Proxy and Implementation. 

ProxyAmdin explained in [OpenZeppelin docs](https://docs.openzeppelin.com/upgrades-plugins/1.x/faq#what-is-a-proxy-admin): 

> What is a proxy admin?
> 
> A ProxyAdmin is a contract that acts as the owner of all your proxies. Only one per network gets deployed. When you start your project, the ProxyAdmin is owned by the deployer address, but you can transfer ownership of it by calling transferOwnership. 

If you transfer ProxyAmin ownership to a multi-sig account, the authority to upgrade the Proxy contract (link proxy to new implementation) is transferred to it.

---

## How to deploy proxy? How to upgrade proxy?

When we first deploy upgradeable contract using OpenZeppelin Upgrades plugin for Hardhat, we deploy three contracts: 

1. deploy "Implementation contract"
2. deploy "ProxyAdmin contract"
3. deploy "Proxy contract"

In the ProxyAdmin contract, Implementation and Proxy is linked. 

When a user calls the proxy contract, the call is delegated to the implementation contract ([delegate call](https://docs.soliditylang.org/en/v0.8.12/introduction-to-smart-contracts.html?highlight=delegatecall#delegatecall-callcode-and-libraries)).


When upgrading the contract, what we do is:

1. deploy a new "Implementation contract"
2. upgrade in "ProxyAdmin contract" by redirecting all calls to proxy to the new Implementation contract.

OpenZeppelin Upgrades plugins for Hardhat/Truffle can help us getting these jobs done.

If you want to know about how to modify a contract to be upgradeable, you can refer to OpenZeppelin docs: [link](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable).

Let's begin to write and deploy an upgradeable smart contract. You can find the repo at Github: https://github.com/fjun99/proxy-contract-example

---

![Task #1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kn83p364u5haxqjtczc5.png)
 

## Task 1: Write upgradable smart contract

### Task 1.1: init hardhat project 

We will use Hardhat, Hardhat Network local testnet and OpenZeppelin Upgrades plugin. 

STEP 1: install hardhat and init a project

```
mkdir solproxy && cd solproxy
yarn init -y
yarn add harthat

yarn hardhat
// choose option: sample typescript 
```

STEP 2: add plugin `@openzeppelin/hardhat-upgrades`

```
yarn add @openzeppelin/hardhat-upgrades
```

Edit `hardhat.config.ts` to use Upgrades plugins.

```
// hardhat.config.ts
import '@openzeppelin/hardhat-upgrades';
```

We will use three functions of hardhat upgrade plugin([API reference link](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades)):

``` js
deployProxy()
upgradeProxy()
prepareUpgrade()
```

### 1.2: Write an upgradable smart contract

We use the `Box.sol` contract from [OpenZeppelin learn guide](https://docs.openzeppelin.com/learn/developing-smart-contracts#setting-up-a-solidity-project). We will build several versions of this contract:

- Box.sol
- BoxV2.sol
- BoxV3.sol
- BoxV4.sol

The big difference between a normal contract and an upgradeable contract is that an upgradeable contract does not have a `constructor()`, [docs link](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable).

``` js
// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
 
contract Box {
    uint256 private value;
 
    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);
 
    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }
 
    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }
}

```

User can `store()` a value to this contract and `retrieve()` it later.


### Task 1.3: unit test script for `Box.sol`

Let's write unit test scripts for `Box.sol`. The following hardhat unit test script is adapted from (
[OpenZeppelin Upgrades: Step by Step Tutorial for Hardhat](https://forum.openzeppelin.com/t/openzeppelin-upgrades-step-by-step-tutorial-for-hardhat/3580)). We made some changes in it.

Edit `test/1.Box.test.ts`:

``` js
// test/1.Box.test.ts
import { expect } from "chai";
import { ethers } from "hardhat"
import { Contract, BigNumber } from "ethers"

describe("Box", function () {
  let box:Contract;

  beforeEach(async function () {
    const Box = await ethers.getContractFactory("Box")
    box = await Box.deploy()
    await box.deployed()
  })

  it("should retrieve value previously stored", async function () {
    await box.store(42)
    expect(await box.retrieve()).to.equal(BigNumber.from('42'))

    await box.store(100)
    expect(await box.retrieve()).to.equal(BigNumber.from('100'))
  })
})

// NOTE: should also add test for event: event ValueChanged(uint256 newValue)

```

Run test:
```
yarn hardhat test test/1.Box.test.ts
```

Results:
```
  Box
    ✓ should retrieve value previously stored
  1 passing (505ms)
✨  Done in 3.34s.
```

---

![Task#2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjnxjtrm9422v4un7scl.png)
 

## Task 2: Deploy upgradeable smart contract

### Task 2.1: write deploy script with "OpenZeppelin Upgrades plugin for Hardhat"

When we write a script to deploy a smart contract, we write:
``` js
  const Greeter = await ethers.getContractFactory("Greeter");
  const greeter = await Greeter.deploy("Hello, Hardhat!");
```

To deploy an upgradeable contract, will call `deployProxy()` instead. Docs can be found at [link](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades#deploy-proxy).

``` js
  const Box = await ethers.getContractFactory("Box")
  const box = await upgrades.deployProxy(Box,[42], { initializer: 'store' })
```

In the second line, we use OpenZeppelin Upgrades plugin to deploy `Box` with an initial value `42` by calling `store()` as initializer.

Edit `scripts/1.deploy_box.ts`

``` js
// scripts/1.deploy_box.ts
import { ethers } from "hardhat"
import { upgrades } from "hardhat"

async function main() {

  const Box = await ethers.getContractFactory("Box")
  console.log("Deploying Box...")
  const box = await upgrades.deployProxy(Box,[42], { initializer: 'store' })

  console.log(box.address," box(proxy) address")
  console.log(await upgrades.erc1967.getImplementationAddress(box.address)," getImplementationAddress")
  console.log(await upgrades.erc1967.getAdminAddress(box.address)," getAdminAddress")    
}

main().catch((error) => {
  console.error(error)
  process.exitCode = 1
})

```

Explanation:

- We deploy an upgradeable contract `Box.sol` using `upgrades.deployProxy()`.
- Three contracts will be deployed: Implementation, ProxyAdmin, Proxy. We log their addresses for inspection.

### Task 2.2: deploy the contract to local testnet

Let's run the deploy script in local testnet.

STEP 1: run a stand-alone hardhat testnet in another terminal: 

```
yarn hardhat node
```

STEP 2: run the deploy script

```
yarn hardhat run scripts/1.deploy_box.ts --network localhost
```

Results:
```
Deploying Box...
0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0  box(proxy) address
0x5FbDB2315678afecb367f032d93F642f64180aa3  getImplementationAddress
0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512  getAdminAddress
✨  Done in 3.83s.
```

Users can interact with the box contract through box(proxy) address: `0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0`.

Note: if you run this deployment for several times, you can find that ProxyAdmin is always the same: `0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512`

### Task 2.3: test whether `Box.sol` using proxy pattern works correctly

Users interact with the implementation contract through the box(proxy) contract. 

To make sure that they work correctly, let's add unit test for this scenario. In the unit test, we deploy our contract using `upgrades.deployProxy()` and interact through the box(proxy) contract.

Edit `test/2.BoxProxy.test.ts`
``` js
// test/2.BoxProxy.test.ts
import { expect } from "chai"
import { ethers, upgrades } from "hardhat"
import { Contract, BigNumber } from "ethers"

describe("Box (proxy)", function () {
  let box:Contract

  beforeEach(async function () {
    const Box = await ethers.getContractFactory("Box")
    //initialize with 42
    box = await upgrades.deployProxy(Box, [42], {initializer: 'store'})
    })

  it("should retrieve value previously stored", async function () {    
    // console.log(box.address," box(proxy)")
    // console.log(await upgrades.erc1967.getImplementationAddress(box.address)," getImplementationAddress")
    // console.log(await upgrades.erc1967.getAdminAddress(box.address), " getAdminAddress")   

    expect(await box.retrieve()).to.equal(BigNumber.from('42'))

    await box.store(100)
    expect(await box.retrieve()).to.equal(BigNumber.from('100'))
  })
  
})
```

Run test: 
```
yarn hardhat test test/2.BoxProxy.test.ts
```

Results:
```
  Box (proxy)
    ✓ should retrieve value previously stored
  1 passing (579ms)
✨  Done in 3.12s.`
```

Our box.sol is working correctly now. 

Later, we find that we need an `increment()` function. Instead of re-deploying this contract, migrate data to the new contract and ask all users to access the new contract address. We can upgrade the contract easily thanks to the proxy pattern.

---

![Task #3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0t4f442e8clgay7tukp.png)
 

## Task 3: Upgrade smart contract to BoxV2

### Task 3.1: write new implementation

We write a new version of Box `BoxV2.sol` by inherit `Box.sol`.

Edit `contracts/BoxV2.sol`
``` solidity
// contracts/BoxV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./Box.sol";
 
contract BoxV2 is Box{
    // Increments the stored value by 1
    function increment() public {
        store(retrieve()+1);
    }
}
```

### Task 3.2: test script for normal deployment

We write a unit test script to test BoxV2 when deployed locally. 

Edit `test/3.BoxV2.test.ts`

``` js
// test/3.BoxV2.test.ts
import { expect } from "chai"
import { ethers } from "hardhat"
import { Contract, BigNumber } from "ethers"

describe("Box V2", function () {
  let boxV2:Contract

  beforeEach(async function () {
    const BoxV2 = await ethers.getContractFactory("BoxV2")
    boxV2 = await BoxV2.deploy()
    await boxV2.deployed()
  });

  it("should retrieve value previously stored", async function () {
    await boxV2.store(42)
    expect(await boxV2.retrieve()).to.equal(BigNumber.from('42'))

    await boxV2.store(100)
    expect(await boxV2.retrieve()).to.equal(BigNumber.from('100'))
  });

  it('should increment value correctly', async function () {
    await boxV2.store(42)
    await boxV2.increment()
    expect(await boxV2.retrieve()).to.equal(BigNumber.from('43'))
  })

})

```

Run test:
```
yarn hardhat test test/3.BoxV2.test.ts
```

results:
```
  Box V2
    ✓ should retrieve value previously stored
    ✓ should increment value correctly
  2 passing (579ms)
✨  Done in 3.38s.
```

### Task 3.3: test script for upgradeable deployment

We write a unit test script for BoxV2 deployed in proxy pattern:

- first, we deploy the Box.sol
- then we upgrade it to BoxV2.sol
- test whether BoxV2 works correctly.

Edit `test/4.BoxProxyV2.test.ts`:

``` js
// test/4.BoxProxyV2.test.ts
import { expect } from "chai"
import { ethers, upgrades } from "hardhat"
import { Contract, BigNumber } from "ethers"

describe("Box (proxy) V2", function () {
  let box:Contract
  let boxV2:Contract

  beforeEach(async function () {
    const Box = await ethers.getContractFactory("Box")
    const BoxV2 = await ethers.getContractFactory("BoxV2")

    //initilize with 42
    box = await upgrades.deployProxy(Box, [42], {initializer: 'store'})
    // console.log(box.address," box/proxy")
    // console.log(await upgrades.erc1967.getImplementationAddress(box.address)," getImplementationAddress")
    // console.log(await upgrades.erc1967.getAdminAddress(box.address), " getAdminAddress")   

    boxV2 = await upgrades.upgradeProxy(box.address, BoxV2)
    // console.log(boxV2.address," box/proxy after upgrade")
    // console.log(await upgrades.erc1967.getImplementationAddress(boxV2.address)," getImplementationAddress after upgrade")
    // console.log(await upgrades.erc1967.getAdminAddress(boxV2.address)," getAdminAddress after upgrade")   
  })

  it("should retrieve value previously stored and increment correctly", async function () {
    expect(await boxV2.retrieve()).to.equal(BigNumber.from('42'))

    await boxV2.increment()
    //result = 42 + 1 = 43
    expect(await boxV2.retrieve()).to.equal(BigNumber.from('43'))

    await boxV2.store(100)
    expect(await boxV2.retrieve()).to.equal(BigNumber.from('100'))
  })

})
```

Run test:
```
yarn hardhat test test/4.BoxProxyV2.test.ts
```

Results:
```
  Box (proxy) V2
    ✓ should retrieve value previously stored and increment correctly


  1 passing (617ms)

✨  Done in 3.44s.
```

### Task 3.4: write upgrade script

In sub-task 2.2, we deploy Box(proxy) to `0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0`. 

In this sub-task, we will upgrade it to BoxV2 (deploy a new contract , and link proxy to a new implementation contract in ProxyAdmin):

Edit 
``` js
// scripts/2.upgradeV2.ts
import { ethers } from "hardhat";
import { upgrades } from "hardhat";

const proxyAddress = '0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0'

async function main() {
  console.log(proxyAddress," original Box(proxy) address")
  const BoxV2 = await ethers.getContractFactory("BoxV2")
  console.log("upgrade to BoxV2...")
  const boxV2 = await upgrades.upgradeProxy(proxyAddress, BoxV2)
  console.log(boxV2.address," BoxV2 address(should be the same)")

  console.log(await upgrades.erc1967.getImplementationAddress(boxV2.address)," getImplementationAddress")
  console.log(await upgrades.erc1967.getAdminAddress(boxV2.address), " getAdminAddress")    
}

main().catch((error) => {
  console.error(error)
  process.exitCode = 1
})

```

### Task 3.5: run the upgrade script

We will start over from the beginning.

STEP 1: run a new local testnet in another terminal:
```
yarn hardhat node
```

STEP 2: deploy Box V1
```
yarn hardhat run scripts/1.deploy_box.ts --network localhost
```

The box(proxy) will be deployed to: `0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0`.

STEP 3: upgrade to Box V2
```
yarn hardhat run scripts/2.upgradeV2.ts --network localhost
```

results:
```
0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0  original Box(proxy) address
upgrade to BoxV2...
0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0  BoxV2 address(should be the same)
0x5FC8d32690cc91D4c39d9d3abcBD16989F875707  getImplementationAddress
0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512  getAdminAddress
✨  Done in 3.64s.
```

---

![Task #4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0tdtn1692l6olxckfb74.png)
 

## Task 4: Play with contract in hardhat console

Let's play with Box(proxy) contract in hardhat console.

### Task 4.1: play with the proxy contract 

Run hardhat console connecting to local testnet. 

```
yarn hardhat console --network localhost
```

In hardhat console: 

``` js
address = '0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0'
boxv2 = await ethers.getContractAt("BoxV2", address)
await boxv2.retrieve()
//BigNumber { value: "42" }

await boxv2.increment()
// tx response
// {
//  hash: '0x3e8c9dd8842d3315cadad2a80b592ac369e644edc5cec16f7a22c76d49e4b921',
//  blockNumber: 6,

await boxv2.retrieve()
//BigNumber { value: "43" }

await boxv2.store(100)
// tx response ...
await boxv2.retrieve()
//BigNumber { value: "100" }
```

### Task 4.2: try to interact with the implementation contact

Let's try to interact with the implementation contact to see what's happening.

``` js
addressimp = '0x5fc8d32690cc91d4c39d9d3abcbd16989f875707'
boximp = await ethers.getContractAt("BoxV2", addressimp)

await boximp.retrieve()
//BigNumber { value: "0" }
await boximp.increment()
// tx response ...
await boximp.retrieve()
BigNumber { value: "1" }
```

We will find that the original value in the implementation is `0`. This is because the data is stored in the context of Proxy contract (`0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0`) under the hood, not in the context of Implementation contract(`0x5fc8d32690cc91d4c39d9d3abcbd16989f875707`).

---

![Task #5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/88lla754tzk9jtaajrfo.png)
 

## Task 5: Build BoxV3 to add new state variable

We can add new state variables as long as you don't change the the layout of the state variables. 

To put it simply, you can add a new one. We will add a state variable `string public name`.

### Task 5.1: write `BoxV3.sol`

We write `BoxV3.sol` by inheriting `BoxV2`:

``` js
// contracts/BoxV3.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./BoxV2.sol";
 
contract BoxV3 is BoxV2{
    string public name;
 
    event NameChanged(string name);
    function setName(string memory _name) public {
        name = _name;
        emit NameChanged(name);
    }
}
```

### Task 5.2: write test for proxy deployment

We skip local unit test here. We will test the proxy deployment only.

``` js
// test/5.BoxProxyV3.test.ts
import { expect } from "chai"
import { ethers, upgrades } from "hardhat"
import { Contract, BigNumber } from "ethers"

describe("Box (proxy) V3 with name", function () {
  let box:Contract
  let boxV2:Contract
  let boxV3:Contract

  beforeEach(async function () {
    const Box = await ethers.getContractFactory("Box")
    const BoxV2 = await ethers.getContractFactory("BoxV2")
    const BoxV3 =  await ethers.getContractFactory("BoxV3")

    //initialize with 42
    box = await upgrades.deployProxy(Box, [42], {initializer: 'store'})
    boxV2 = await upgrades.upgradeProxy(box.address, BoxV2)
    boxV3 = await upgrades.upgradeProxy(box.address, BoxV3)
  })

  it("should retrieve value previously stored and increment correctly", async function () {
    expect(await boxV2.retrieve()).to.equal(BigNumber.from('42'))
    await boxV3.increment()
    expect(await boxV2.retrieve()).to.equal(BigNumber.from('43'))

    await boxV2.store(100)
    expect(await boxV2.retrieve()).to.equal(BigNumber.from('100'))
  })

  it("should set name correctly in V3", async function () {
    expect(await boxV3.name()).to.equal("")

    const boxname="my Box V3"
    await boxV3.setName(boxname)
    expect(await boxV3.name()).to.equal(boxname)
  })

})

// NOTE: should also add test for event: event NameChanged(string name)
```

Run Test:
```
yarn hardhat test test/5.BoxProxyV3.test.ts
```

Results: 
```
  Box (proxy) V3 with name
    ✓ should retrieve value previously stored and increment correctly
    ✓ should set name correctly in V3
  2 passing (748ms)
✨  Done in 3.12s.
```

### Task 5.3: write upgrade script

Edit `scripts/3.upgradeV3.ts`:

``` js
// scripts/3.upgradeV3.ts
import { ethers } from "hardhat";
import { upgrades } from "hardhat";

const proxyAddress = '0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0'
// const proxyAddress = '0x1CD0c84b7C7C1350d203677Bb22037A92Cc7e268'
async function main() {
  console.log(proxyAddress," original Box(proxy) address")
  const BoxV3 = await ethers.getContractFactory("BoxV3")
  console.log("upgrade to BoxV3...")
  const boxV3 = await upgrades.upgradeProxy(proxyAddress, BoxV3)
  console.log(boxV3.address," BoxV3 address(should be the same)")

  console.log(await upgrades.erc1967.getImplementationAddress(boxV3.address)," getImplementationAddress")
  console.log(await upgrades.erc1967.getAdminAddress(boxV3.address), " getAdminAddress")    
}

main().catch((error) => {
  console.error(error)
  process.exitCode = 1
})

```

### Task 5.4: deploy and interact with BoxV3

STEP 1: upgrade to BoxV3
```
yarn hardhat run scripts/3.upgradeV3.ts --network localhost
```

results:
```
0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0  original Box(proxy) address
upgrade to BoxV3...
0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0  BoxV3 address(should be the same)
0xa513E6E4b8f2a923D98304ec87F64353C4D5C853  getImplementationAddress
0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512  getAdminAddress
✨  Done in 3.52s.
```

STEP 2: play with V3 in hardhat console

```
yarn hardhat console --network localhost
```

``` js
address = '0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0'
boxv3 = await ethers.getContractAt("BoxV3", address)
await boxv3.retrieve()
//BigNumber { value: "42" }

await boxv3.setName("mybox")
// tx response
await boxv3.name()
//'mybox'
```

STEP 3: try to play with the implementation contract directly again

``` js
addressimp = '0xa513E6E4b8f2a923D98304ec87F64353C4D5C853'
boximp = await ethers.getContractAt("BoxV3", addressimp)
await boximp.retrieve()
//BigNumber { value: "0" }
await boximp.name()
//''
```


You can find explanations about data change in openzeppelin docs[openzeppelin docs](https://docs.openzeppelin.com/learn/upgrading-smart-contracts#upgrading):

> Due to technical limitations, when you upgrade a contract to a new version you cannot change the storage layout of that contract.

> This means that, if you have already declared a state variable in your contract, you cannot remove it, change its type, or declare another variable before it. In our Box example, it means that we can only add new state variables after value.

---

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xkt0ni0lqkldju4cmq6.png)
 

## Task 6: Write BoxV4 with prepareUpgrade script

We write BoxV4 and prepare upgrade script for Task 7 in which we will deploy and upgrade these versions of Box to public testnet Ropsten manually call `ProxyAdmin.upgrade()`.

Let's write `BoxV4.sol`, unit test and upgrade script.

### Task 6.1: write BoxV4.sol

We write a BoxV4 which change how we interact with `name` state variable:

- We change the state variable `name` from `public` to `private`.
- When user `getName()`, a prefix is added.

``` js
// contracts/BoxV4.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./BoxV2.sol";
 
contract BoxV4 is BoxV2{
    string private name;
 
    event NameChanged(string name);
    function setName(string memory _name) public {
        name = _name;
        emit NameChanged(name);
    }

   function getName() public view returns(string memory){
      return string(abi.encodePacked("Name: ",name));
    }
}
```

### Task 6.2 write test for proxy deployment

Edit `test/6.BoxProxyV4.test.ts`:

``` js
// test/6.BoxProxyV4.test.ts
import { expect } from "chai"
import { ethers, upgrades } from "hardhat"
import { Contract, BigNumber } from "ethers"

describe("Box (proxy) V4 with getName", function () {
  let box:Contract
  let boxV2:Contract
  let boxV3:Contract
  let boxV4:Contract
    
  beforeEach(async function () {
    const Box = await ethers.getContractFactory("Box")
    const BoxV2 = await ethers.getContractFactory("BoxV2")
    const BoxV3 =  await ethers.getContractFactory("BoxV3")
    const BoxV4 =  await ethers.getContractFactory("BoxV4")

    //initialize with 42
    box = await upgrades.deployProxy(Box, [42], {initializer: 'store'})
    boxV2 = await upgrades.upgradeProxy(box.address, BoxV2)
    boxV3 = await upgrades.upgradeProxy(box.address, BoxV3)
    boxV4 = await upgrades.upgradeProxy(box.address, BoxV4)
  })

  it("should retrieve value previously stored and increment correctly", async function () {
    expect(await boxV4.retrieve()).to.equal(BigNumber.from('42'))
    await boxV4.increment()
    expect(await boxV4.retrieve()).to.equal(BigNumber.from('43'))

    await boxV2.store(100)
    expect(await boxV2.retrieve()).to.equal(BigNumber.from('100'))
  })

  it("should setName and getName correctly in V4", async function () {
    //name() removed, getName() now
    // expect(boxV4).to.not.have.own.property("name")
    expect(boxV4.name).to.be.undefined
    expect(await boxV4.getName()).to.equal("Name: ")
    
    const boxname="my Box V4"
    await boxV4.setName(boxname)
    expect(await boxV4.getName()).to.equal("Name: "+boxname)
  })

})

```

Run Test:
```
yarn hardhat test test/6.BoxProxyV4.test.ts
```

Results:
```
  Box (proxy) V4 with getName
    ✓ should retrieve value previously stored and increment correctly
    ✓ should setName and getName correctly in V4
  2 passing (771ms)
✨  Done in 2.46s.
```

### Task 6.3: write script to prepare upgrade

We will write this script using `upgrades.prepareUpgrade`. 

When calling `upgrades.upgradeProxy()`, two jobs are done:

- an implementation contract is deployed
- ProxyAdmin `upgrade()` is called to link Proxy and implementation contract.

When calling `upgrades.prepareUpgrade()`, only the first job is done, and the second is left for developers to do manually.

Edit `scripts/4.prepareV4.ts`
``` js
// scripts/4.prepareV4.ts
import { ethers } from "hardhat";
import { upgrades } from "hardhat";

const proxyAddress = '0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0'
// const proxyAddress = '0x1CD0c84b7C7C1350d203677Bb22037A92Cc7e268'
async function main() {
  console.log(proxyAddress," original Box(proxy) address")
  const BoxV4 = await ethers.getContractFactory("BoxV4")
  console.log("Preparing upgrade to BoxV4...");
  const boxV4Address = await upgrades.prepareUpgrade(proxyAddress, BoxV4);
  console.log(boxV4Address, " BoxV4 implementation contract address")
}

main().catch((error) => {
  console.error(error)
  process.exitCode = 1
})
```

Run deployment:
```
yarn hardhat run scripts/4.prepareV4.ts --network localhost
```

Results:
```
0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0  original Box(proxy) address
Preparing upgrade to BoxV4...
0x610178dA211FEF7D417bC0e6FeD39F05609AD788  BoxV4 implementation contract address
✨  Done in 3.90s.
```

You can interact with proxy contract (`0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0`) in hardhat console by running `yarn hardhat console --network localhost`

``` js
address = '0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0'
boxv4 = await ethers.getContractAt("BoxV4", address)

await boxv4.getName()
//We will get error:
//ProviderError: Error: Transaction reverted: function selector was not recognized
```
---

![Task #7](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ijc3ij7qtsq1xhomzww6.png)
 

## Task 7: Deploy Box to public testnet Ropsten

### Task 7.1: preparations to deploy to Ropsten

We can deploy smart contract to Ropsten directly using Hardhat. 

STEP 1: Edit Alchemy/Infura endpoint in `.env`

```
ROPSTEN_URL=https://eth-ropsten.alchemyapi.io/v2/{YOURS}
PRIVATE_KEY={YOURS HERE}
```

STEP 2: Make sure you have Ropsten set in `hardhat.config.ts`:

``` json
  networks: {
    ropsten: {
      url: process.env.ROPSTEN_URL || "",
      accounts:
        process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
    },
  },
```

### Task 7.2: deploy Box V1 

Run deploy script:
```
yarn hardhat run scripts/1.deploy_box.ts --network ropsten
```

![deploy box v1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yz5yp9d7yfyrw9dyd0dv.png)
 

Three contracts are deployed:

- `0x7fcb5f0898ee1394c6cb44e3a62b9e9fc19d0e1c`, implementation contract
- `0x9ce0fdc88df321c804ed0ad9cefe87d97a30479e`, ProxyAmin contract
- `0x1CD0c84b7C7C1350d203677Bb22037A92Cc7e268`, proxy contract

We can interact with the proxy contract from hardhat console by running `yarn hardhat console --network ropsten`

In console:
``` js
address = '0x1CD0c84b7C7C1350d203677Bb22037A92Cc7e268'
box = await ethers.getContractAt("Box", address)
await box.retrieve()
//BigNumber { value: "43" }
```

### Task 7.3: upgrade to Box V2

Run our upgrade script:
```
yarn hardhat run scripts/2.upgradeV2.ts --network ropsten
```

The script does two jobs:

- deploy new implementation contract
- call ProxyAmdin.upgrade()

![upgrade to BoxV2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7wqohz4tbkfpry9h05j.png)
 

Let's interact with the Box(proxy) in console:
``` js
address = '0x1CD0c84b7C7C1350d203677Bb22037A92Cc7e268'
box = await ethers.getContractAt("BoxV2", address)

await box.increment()
//wait for tx to be mined in a new block...

await box.retrieve()
//BigNumber { value: "44" }
```

### Task 7.4: upgrade to Box V3 

Run our upgrade script:
```
yarn hardhat run scripts/3.upgradeV3.ts --network ropsten
```

Let's interact with the Box(proxy) with BoxV3 in console:

``` js
address = '0x1CD0c84b7C7C1350d203677Bb22037A92Cc7e268'
box = await ethers.getContractAt("BoxV3", address)

box = await ethers.getContractAt("BoxV3", address)
await box.setName("mybox")
//wait for tx to be mined in a new block...
```


![upgrade to V3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tf2syipy44sjq0jixuge.png)
 

### Task 7.5: prepare upgrade to Box V4

Run the prepare upgrade script:
```
yarn hardhat run scripts/4.prepareV4.ts --network ropsten
```

This script will deploy a BoxV4 contract at `0xA0726E6e045f84dEe8D7cA4CdD427A68dd336458`.

We will upgrade in block explorer `https://ropsten.etherscan.io` manually. 

ProxyAdmin is already verified on Etherscan block explorer. In ProxyAmdin contract write page, connect to wallet and run `upgrade()` like in the screenshot.


![prepare BoxV4 and manually upgrade](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zans8cz2y6wt3zgn63s9.png)
 

``` js
address = '0x1CD0c84b7C7C1350d203677Bb22037A92Cc7e268'
box = await ethers.getContractAt("BoxV4", address)

box = await ethers.getContractAt("BoxV4", address)
await box.getName()
//'Name: mynewbox'
```

### Task 7.6: read ProxyAdmin contract 

You can also read ProxyAdmin contract to get more information about the Box(proxy) contract:

- getProxyAdmin
- getProxyImplementation
- owner

![read ProxyAdmin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0bsn4dnef869qxoriv94.png)
 

If you would like to verify Box, BoxV2, BoxV3, BoxV4 contract in Etherscan, you can use hardhat plugin `hardhat-etherscan`.

Install plugin:
```
yarn add @nomiclabs/hardhat-etherscan
```

Verify BoxV4: 
```
yarn hardhat verify 0xA0726E6e045f84dEe8D7cA4CdD427A68dd336458 --network ropsten
```

--- 

You have built and deploy proxy contract in local testnet and public testnet Ropsten in 7 tasks. You can continue to do more:

- Use OpenZeppelin Defender to manage upgrading.

- Transfer ProxyAdmin owner to a multi-sig address such as Gnosis Safe multi-sig wallet.

---

References you may be interested in:

- https://docs.openzeppelin.com/learn/upgrading-smart-contracts#upgrading
- https://docs.openzeppelin.com/defender/guide-upgrades
- https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies
- https://www.preethikasireddy.com/post/the-hardest-concept-for-developers-to-grasp-about-web-3-0
- https://forum.openzeppelin.com/t/openzeppelin-upgrades-step-by-step-tutorial-for-hardhat/3580
- https://blog.openzeppelin.com/proxy-patterns/
- https://blog.trailofbits.com/2018/09/05/contract-upgrade-anti-patterns/
- https://blog.openzeppelin.com/the-state-of-smart-contract-upgrades/

---


### Tutorial List:

#### 1. A Concise Hardhat Tutorial(3 parts) 
https://dev.to/yakult/a-concise-hardhat-tutorial-part-1-7eo

#### 2. Understanding Blockchain with `Ethers.js`(5 parts)
https://dev.to/yakult/01-understanding-blockchain-with-ethersjs-4-tasks-of-basics-and-transfer-5d17

#### 3. Tutorial : build your first DAPP with Remix and Etherscan (7 Tasks)
https://dev.to/yakult/tutorial-build-your-first-dapp-with-remix-and-etherscan-52kf

#### 4. Tutorial: build DApp with Hardhat, React and Ethers.js (6 Tasks)
https://dev.to/yakult/a-tutorial-build-dapp-with-hardhat-react-and-ethersjs-1gmi

#### 5. Tutorial: build DAPP with Web3-React and SWR
https://dev.to/yakult/tutorial-build-dapp-with-web3-react-and-swr-1fb0

#### 6. Tutorial: write upgradeable smart contract (proxy) using OpenZeppelin(7 Tasks)
https://dev.to/yakult/tutorial-write-upgradeable-smart-contract-proxy-contract-with-openzeppelin-1916

#### 7. Tutorial: Build a NFT marketplace DApp like Opensea(5 Tasks)
https://dev.to/yakult/tutorial-build-a-nft-marketplace-dapp-like-opensea-3ng9

---

If you find this tutorial helpful, follow me at Twitter [@fjun99](https://twitter.com/fjun99)