티스토리 뷰
- 프록시 패턴은 업그레이드 가능한 스마트 컨트랙트를 구현하는 방법입니다.
- logic contract 앞에 proxy contract 를 두고,
- proxy 컨트랙트의 스토리지 데이터를 사용함으로써 logic contract가 업데이트 되더라도 기존의 데이터를 유지한 채 업데이트된 논리적 기능을 호출할 수 있는 방식입니다. (logic 컨트랙트에서 데이터를 읽을 때도 프록시 컨트랙트의 스토리지로부터 가져오고, 데이터를 write할 때에도 프록시의 스토리지에 기록합니다.)
- 사용자는 proxy contract 를 호출하고 proxy contract 의 fallback 함수를 통해서 logic contract 를 delegatecall 합니다.
- logic contract 를 업데이트할 때에는 관리가자 ProxyAdmin 컨트랙트의 관리자만 호출 가능한 upgrade 함수를 통해서 새로운 기능의 logic contract 를 배포하고 이후에는 proxy contract 가 새롭게 업그레이드된 logic contract 를 호출합니다.
패키지 설치
npm i @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan @openzeppelin/hardhat-upgrades ethers hardhat
npm i -D @openzeppelin/contracts @openzeppelin/contracts-upgradeable
Logic Contract 작성
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;
contract Box {
uint256 public val;
// constructor(uint _val) {
// val = _val;
// }
function initialize(uint256 _val) external {
val = _val;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;
contract BoxV2 {
uint256 public val;
// function initialize(uint _val) external {
// val = _val;
// }
function inc() external {
val += 1;
}
}
V1 배포 스크립트 작성
// .env
INFURA_API_KEY=
ETHERSCAN_API_KEY=
PRI_KEY=
// hardhat.config.js
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-web3");
require("@nomiclabs/hardhat-ethers");
require("@openzeppelin/hardhat-upgrades");
require("@nomiclabs/hardhat-etherscan");
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
networks: {
dev: {
url: `http://localhost:8545`,
chainId: 1337, // config standard
},
mumbai: {
url: `https://polygon-mumbai.infura.io/v3/${process.env.INFURA_API_KEY}`,
accounts: [process.env.PRI_KEY],
},
mainnet: {
url: `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
accounts: [process.env.PRI_KEY],
},
ropsten: {
url: `https://ropsten.infura.io/v3/${process.env.INFURA_API_KEY}`,
accounts: [process.env.PRI_KEY],
// gasPrice: 470000000000,
},
rinkeby: {
url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`,
accounts: [process.env.PRI_KEY],
// gasPrice: 470000000000,
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
solidity: "0.8.13",
};
// scripts/deploy_box_v1.js
const { ethers, upgrades } = require("hardhat");
async function main() {
const Box = await ethers.getContractFactory("Box");
console.log("Deploying Box...");
const box = await upgrades.deployProxy(Box, [42], {
initializer: "initialize",
});
await box.deployed();
console.log("Box deployed to:", box.address);
}
main();
V1 배포
env $(cat .env) npx hardhat run --network ropsten scripts/deploy_box_v1.js
Box deployed to: 0x2dB039b6B1e08e8F98eCe6A1018F43502c1Dd545
배포한 지갑 주소의 트랜잭션 내역을 확인하면 3개의 tx가 발생하였음을 확인할 수 있습니다. 3개의 컨트랙트 배포가 이루어졌습니다.
세 번째로 배포된 컨트랙트 TransparentUpgradeableProxy 컨트랙트 (0x2dB039b6B1e08e8F98eCe6A1018F43502c1Dd545)
두 번째로 배포된 컨트랙트 ProxyAdmin 컨트랙트
(0xD600B049Ee83F68beCDa1D6C15Fe5FE7555B9B0E)
첫 번째로 배포된 컨트랙트 Box V1 컨트랙트(logic contract)
(0xA697B47cB403502A3eC6C6E97ce5c751f578AA8b)
Verify V1 Logic Contract (etherscan)
env $(cat .env) npx hardhat verify --network ropsten 0xA697B47cB403502A3eC6C6E97ce5c751f578AA8b
Verify V1 Logic Contract (etherscan) - 에러 발생
env $(cat .env) npx hardhat verify --network ropsten 0xA697B47cB403502A3eC6C6E97ce5c751f578AA8b
Nothing to compile
An unexpected error occurred:
[Error: ENOENT: no such file or directory, open '/Users/hyun/dev/blockchain/hardhat-console/artifacts/build-info/f6d72e584f9c6e176d0b340e8f9097a9.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/hyun/dev/blockchain/hardhat-console/artifacts/build-info/f6d72e584f9c6e176d0b340e8f9097a9.json'
}
Verify V1 Logic Contract (etherscan) - 해결 방법
env $(cat .env) npx hardhat clean
Verify V1 Logic Contract (etherscan) - val 값 확인
// scripts/deploy_box_v1.js
...
const box = await upgrades.deployProxy(Box, [42], {
initializer: "initialize",
});
...
Box V1 컨트랙트를 배포할 때 초기 값으로 42를 전달했었습니다. 그런데 Etherscan 상에서 Box 컨트랙트와 Proxy 컨트랙트의 storage 데이터를 확인하면 0 이거나 값이 존재하지 않는 것을 확인할 수 있습니다.
Box 컨트랙트는 logic contract 이기 때문에 val 값이 0 일 수 있지만 Proxy 컨트랙트에는 데이터가 있어야 합니다.
Etherscan에서 이 컨트랙트가 Proxy 컨트랙트라는 사실을 알리면 의도했던 val 데이터를 확인할 수 있습니다.
V2 업그레이드 스크립트 작성
// scripts/upgrade_box_v2.js
const { ethers, upgrades } = require("hardhat");
const PROXY = "0x2dB039b6B1e08e8F98eCe6A1018F43502c1Dd545"; // 프록시 컨트랙트
async function main() {
const BoxV2 = await ethers.getContractFactory("BoxV2");
console.log("Upgrading Box...");
await upgrades.upgradeProxy(PROXY, BoxV2);
console.log("Box upgraded");
}
main();
업그레이드
env $(cat .env) npx hardhat run --network ropsten scripts/upgrade_box_v2.js
업그레이드 - 에러
transaction underpriced 에러가 발생할 수 있는데, 해당 에러가 발생하면 에러가 안날 때까지 다시 명령어를 실행해 줍니다.
업그레이드를 수행한 지갑 주소의 트랜잭션 내역을 확인해보면, 2개의 트랜잭션이 발생한 것을 확인할 수 있습니다.
두 번째 트랜잭션은 앞서 배포한 ProxyAdmin 컨트랙트의 upgrade 함수를 호출하는 트랜잭션입니다.
첫 번째 트랜잭션은 Box V2 컨트랙트를 배포하는 트랜잭션입니다.
Box V2 컨트랙트: 0x7F380b064694F525cE66c46082B0B520502bb78C
Verify Box V2 Contract
env $(cat .env) npx hardhat verify --network ropsten 0x7F380b064694F525cE66c46082B0B520502bb78C
새롭게 배포한 Box V2 컨트랙트를 Verify 합니다.
Etherscan에서 프록시 컨트랙트의 Logic Contract가 업데이트되었다는 것을 알립니다.
Box V2에 추가한 inc 함수를 호출할 수 있게 된 것을 확인할 수 있습니다.
inc 함수를 호출하고 다시 val 값을 확인하면 1 증가한 43이 된 것을 볼 수 있습니다.
참고
https://www.youtube.com/watch?v=JgSj7IiE4jA
'블록체인 개발 > Solidity' 카테고리의 다른 글
mapping 타입에서 겪었던 에러 (0) | 2022.01.02 |
---|---|
interface (0) | 2022.01.02 |
call (0) | 2022.01.02 |
fallback, receive (0) | 2022.01.02 |
send, transfer, call (0) | 2022.01.02 |
- Total
- Today
- Yesterday
- class
- 앱 아이콘
- ethers.js
- erc20
- 블록 탐색기
- typescript
- swr
- Truffle
- 블록체인
- avalanchego
- caver-js
- caver.js
- web3.js
- nft
- Hardhat
- 스마트 컨트랙트
- ganache
- Android
- interface
- web3-token
- eslint
- metamask-extension
- ERC721
- Upgradeable Contracts
- web3
- Call
- Proxy Pattern
- Flutter
- JWT
- 이더리움
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |