# Homework
## HW 1 寫出可以更新的智能合約,如:Uniswap v1 -> v2 -> v3
## 說明
使用 OpenZeppelin Upgrades 的架構,它會自動幫我們產出以下檔案 :
1. ProxyAdmin.sol
2. TransparentUpgradeableProxy.sol (這裡簡稱 Proxy.sol)
* `user` 透過 `Proxy.sol` 這個介面操作 `Box.sol` 的邏輯功能,`user` **不會感覺到 `Proxy.sol` 的存在**,因為 `Proxy.sol` 會綁定 `Box.sol` 的合約地址,並使用 delegatecall 的方式呼叫。
* 如果 `Box.sol` 的有**漏洞**或想**新增功能**,可以新增一個 `BoxV2.sol`,並透過 `ProxyAdmin.sol` 核准 `Proxy.sol` 綁定 `BoxV2.sol` 的合約地址。
## 架構
```
# version 1
user ---> Proxy.sol ---> Box.sol
^
^
^
delegatecall
# version 2
user ---> Proxy.sol ---> BoxV2.sol
^
^ update()
^
owner ---> ProxyAdmin.sol
```
## 範例代碼,解釋 delegatecall 的實作
[delegatecall 的實作](/ItGDRnYYTCGLCV2D314l6Q)
---
## 基本安裝
```shell=
mkdir mycontract && cd mycontract
npm init -y
# 快速創建環境
npx hardhat
npm install --save-dev @openzeppelin/hardhat-upgrades
npm install --save-dev @nomiclabs/hardhat-ethers ethers
npm install --save-dev @nomiclabs/hardhat-etherscan
# 使用環境變數 .env
npm install dotenv --save
```
---
## 申請部屬網路 (rinkeby、ropsten) 的 URL、API key
1. [Infura](https://infura.io/) 申請 rinkeby 的 URL
https://rinkeby.infura.io/v3/ca225f76e29a45...
2. [etherscan](https://etherscan.io/) 申請 API key
4ZWQ9RG6FDGCF4Z1NJFQ79D...
3. 導出錢包的私鑰
0xsadfa231...
---
## 創建 .env 檔案
.env
```shell=
URL=https://rinkeby.infura.io/v3/ca22...
PRIVATE_KEY=545asfsa45f...
ETHERSCAN_API_KEY=4ZWQ9RG6...
```
---
## 設定檔
hardhat.config.js
```javascript=
// hardhat.config.js
require("@nomiclabs/hardhat-ethers");
require("@openzeppelin/hardhat-upgrades");
require("@nomiclabs/hardhat-etherscan");
require('dotenv').config();
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.8.0",
networks: {
rinkeby: {
url: process.env.URL,
accounts: [process.env.PRIVATE_KEY],
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
};
```
---
## 創建第一份合約
contracts/Box.sol
```solidity=
// 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;
}
}
```
---
## Box.sol 合約佈署程式
scripts/deploy.js
```javascript=
async function main() {
const Box = await ethers.getContractFactory("Box")
console.log("Deploying Box, ProxyAdmin, and then Proxy...")
const proxy = await upgrades.deployProxy(Box, [42], { initializer: 'store' })
console.log("Proxy of Box deployed to:", proxy.address)
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error)
process.exit(1)
})
```
---
## 開始編譯和部屬到網路,這裡我們選的是 rinkeby 測試網路
```shell=
> npx hardhat run --network rinkeby scripts/deploy.js
Compiling 3 files with 0.8.0
Compilation finished successfully
Deploying Box, ProxyAdmin, and then Proxy...
Proxy of Box deployed to: 0xF5B1dc46054427CABb941393ff4DD0264B5C214f
```
* 成功的話將會佈署 3 個合約
* Box, ProxyAdmin, Proxy(TransparentUpgradeableProxy)
* Proxy 很重要,這是 User 的互動介面
* Proxy address: 0xF5B1dc46054427CABb941393ff4DD0264B5C214f # 記住它
---
## 上傳程式碼 Box.sol
如果要到 etherscan 頁面操作 contract,完成這一步才能在 etherscan 上看到程式碼和操作
```bash=
npx hardhat verify --network rinkeby <Box.sol address>
```
---
## 開啟 console
* Proxy.sol 綁定 Box.sol 合約地址
* 如果不想要命令列操作,完成上傳程式碼,就可以到 etherscan 的頁面做點選綁定的操作
```bash=
$ npx hardhat console --network rinkeby
> const Box = await ethers.getContractFactory("Box")
undefined
> const box = await Box.attach("0xF5B1dc46054427CABb941393ff4DD0264B5C214f")
undefined
> (await box.retrieve()).toString()
'42'
```
---
## 創建第二份合約
contracts/BoxV2.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract BoxV2 {
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;
}
// Increments the stored value by 1
function increment() public {
value = value + 1;
emit ValueChanged(value);
}
}
```
## BoxV2.sol 合約佈署程式
scripts/upgrade.js
```javascript=
async function main() {
// 需要輸入 proxy address
const proxyAddress = '0xF5B1dc46054427CABb941393ff4DD0264B5C214f';
const BoxV2 = await ethers.getContractFactory("BoxV2")
let box = await upgrades.upgradeProxy(proxyAddress, BoxV2)
console.log("Your upgraded proxy is done!", box.address)
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error)
process.exit(1)
})
```
---
## 開始編譯和部屬到網路
```bash=
> npx hardhat run --network rinkeby scripts/upgrade.js
Your upgraded proxy is done! 0xF5B1dc46054427CABb941393ff4DD0264B5C214f
```
---
## 上傳程式碼 BoxV2.sol
如果要到 etherscan 頁面操作 contract,完成這一步才能在 etherscan 上看到程式碼和操作
```bash=
npx hardhat verify --network rinkeby <BoxV2.sol address>
```
## 打開 hardhat console
* Proxy.sol 綁定 BoxV2.sol 合約地址
* 如果不想要命令列操作,完成上傳程式碼,就可以到 etherscan 的頁面做點選綁定的操作
```bash=
> npx hardhat console --network rinkeby
> const BoxV2 = await ethers.getContractFactory("BoxV2")
undefined
> const boxV2 = await BoxV2.attach("0xF5B1dc46054427CABb941393ff4DD0264B5C214f")
undefined
> (await boxV2.retrieve()).toString()
'42'
> await boxV2.increment()
{ hash:
...
> (await boxV2.retrieve()).toString()
'43'
```
這裡更新可能會失敗,多試幾次
---
## 資料來源
* etherscan 頁面操作的方式
https://www.youtube.com/watch?v=JgSj7IiE4jA&list=PLO5VPQH6OWdXhkOvoptGTyQk3KI2EawUc&index=9
* 命令操作的方式
https://www.youtube.com/watch?v=bdXJmWajZRY
* 沒有影片,純文字教學
https://forum.openzeppelin.com/t/openzeppelin-upgrades-step-by-step-tutorial-for-hardhat/3580
* DelegateCall: Calling Another Contract Function in Solidity
https://medium.com/coinmonks/delegatecall-calling-another-contract-function-in-solidity-b579f804178c