티스토리 뷰

728x90

  1. 프록시 패턴은 업그레이드 가능한 스마트 컨트랙트를 구현하는 방법입니다.
  2. logic contract 앞에 proxy contract 를 두고,
  3. proxy 컨트랙트의 스토리지 데이터를 사용함으로써 logic contract가 업데이트 되더라도 기존의 데이터를 유지한 채 업데이트된 논리적 기능을 호출할 수 있는 방식입니다. (logic 컨트랙트에서 데이터를 읽을 때도 프록시 컨트랙트의 스토리지로부터 가져오고, 데이터를 write할 때에도 프록시의 스토리지에 기록합니다.)
  4. 사용자는 proxy contract 를 호출하고 proxy contract 의 fallback 함수를 통해서 logic contract 를 delegatecall 합니다.
  5. 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
링크
«   2025/05   »
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
글 보관함