본문 바로가기
BlockChain

컨트랙트를 Upgradeable Plugin으로 배포해보자 [8]

by parkjp 2023. 8. 31.

 

시작하기 앞서

이번 편에서는 수정이 가능한 Upgradeable 컨트랙트 배포를 진행하려 한다.

구조는 내가 배포한 컨트랙트 앞단에 Upgradeable Proxy 컨트랙트가 존재하여 Proxy 컨트랙트에서 현재 최종버전의 컨트랙트를 바라보는 구조이다.

 

 

Proxy Contract

 

이제 이 구조를 어떻게 배포하는지 알아보자.

 

 

Upgradeable Proxy로 배포하기

새로 contract를 만들어보자. 지난 편에 예시로 만들었던 ERC721로 진행해보겠다.

Upgradeable은 import하는 컨트랙트가 다르다.

 

npm install @openzeppelin/truffle-upgrades
npm install @openzeppelin/contracts-upgradeable

우선 두 패키지를 install 해주자.

 

// TestUpgradeableERC721.sol


// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";

contract TestUpgradeableERC721 is ERC721URIStorageUpgradeable, ReentrancyGuardUpgradeable {
    using CountersUpgradeable for CountersUpgradeable.Counter;
    CountersUpgradeable.Counter private _tokenIds;


    function initialize() initializer public {
        __ERC721_init("item", "ITM");
        __ERC721URIStorage_init();
        __ReentrancyGuard_init();
    }
}

상속하는 컨트랙트가 ERC721URIStorage에서 ERC721URIStorageUpgradeable로 바뀌었다.

그리고 initialize함수로 초기화가 진행된다.

 

// 1_deploy.js

const { deployProxy } = require('@openzeppelin/truffle-upgrades');

const contract = artifacts.require('TestUpgradeableERC721');

module.exports = async function (deployer) {
    const instance = await deployProxy(contract, { deployer });
    console.log('Deployed', instance.address);
};

migration파일도 기존과는 다르게 truffle-upgrades 패키지에서 deployProxy로 진행한다.

첫 배포는 deployProxy지만 두번째 배포는 UpgradeProxy로 진행해야 한다. 그 부분은 아래에서 보여주겠다.

 

truffle migration --network "sepolia" --f 1 --skip-dry-run

migration 파일까지 설정이 되어있으면 위 명령어를 입력하여 실행하자.

** --skip-dry-run은 truffle-config.js 파일에 true로 설정을 해두면 명령어할때마다 적지 않아도 된다.

 

배포를 하게되면 console.log로 찍은 주소와 TestUpgradeableERC721 컨트랙트의 주소를 볼 수 있다.

console.log로 찍은 주소는 proxy 컨트랙트의 주소이다.

우선 TestUpgradeableERC721 컨트랙트의 주소를 sepolia etherscan에서 검색하자.

그렇다면 이전편에서 설명했듯 verify를 진행하고 proxy contract 주소를 검색해보자.

 

proxy contract에서 contract 탭에보면 오른쪽에 More Options라는 버튼이 보일 것이다.

이걸 클릭 후 'Is this a proxy?'를 클릭하자. 그리고 verify를 누르면 된다.

 

여기까지 진행했다면 proxy와 TestUpgradeableERC721 컨트랙트가 인증되어 연결이 되고,

Proxy 컨트랙트에서 Read as Proxy와 Write as Proxy 버튼이 생겼을 것이다.

Read as Proxy버튼을 클릭해보면 현재 어떤 컨트랙트를 implementation하고 있는지 확인 할 수 있다.

 

Proxy Contract

 

 

컨트랙트 업데이트하기

자 이제 위에서 올린 TestUpgradeableERC721컨트랙트를 업데이트 해보자.

 

// TestUpgradeableERC721V2.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";

contract TestUpgradeableERC721V2 is ERC721URIStorageUpgradeable, ReentrancyGuardUpgradeable {
    using CountersUpgradeable for CountersUpgradeable.Counter;
    CountersUpgradeable.Counter private _tokenIds;


    function initialize() initializer public {
        __ERC721_init("item", "ITM");
        __ERC721URIStorage_init();
        __ReentrancyGuard_init();
    }

    function awardItem(address player, string memory tokenURI)
    public
    returns (uint256)
    {
        uint256 newItemId = _tokenIds.current();
        _mint(player, newItemId);
        _setTokenURI(newItemId, tokenURI);

        _tokenIds.increment();
        return newItemId;
    }

    function buy(uint256 tokenId)
    external
    payable nonReentrant
    {
        address buyer = msg.sender;
        address payable seller = payable(ownerOf(tokenId));

        require(buyer != address(0));
        require(msg.value <= address(buyer).balance, "lack of your balance");

        _safeTransfer(seller, buyer, tokenId, "");
        seller.transfer(msg.value);
    }
}

TestUpgradeableERC721V2라고 만들었다.

awardItem과 buy 함수를 추가하였다.

 

// 2_update.js

const { upgradeProxy } = require('@openzeppelin/truffle-upgrades');

const old = artifacts.require('TestUpgradeableERC721');
const updated = artifacts.require('TestUpgradeableERC721V2');

module.exports = async function (deployer) {
    const existing = await old.deployed();
    const instance = await upgradeProxy(existing.address, updated, { deployer });
    console.log('Deployed', instance.address);
};

migration 파일도 새로 2_update.js로 만들었다.

 

truffle migration --network "sepolia" --f 2 --skip-dry-run

2번째 migration을 실행해보자.

TestUpgradeableERC721V2 컨트랙트가 배포가 되는데 배포 후 Proxy 컨트랙트가 바로 V2를 바라보는게 아니라 마찬가지로 verify 과정을 진행해 주어야한다.

 

Proxy Contract not verified

인증이 아직 되지 않았을 때 Proxy Contract에서 보여지는 화면이다.

인증 전에는 이전에 배포한 컨트랙트를 아직 바라보는 상태이다.

 

Proxy Contract verified

인증이 되면 위 화면 처럼 현재 바라보는 컨트랙트 주소와 이전 버전의 컨트랙트 주소를 보여준다.

 

 

 

다음편에서는

여기까지 간단하게 Upgradeable 컨트랙트 배포 방법을 알아보았다.

다음은 web3.js로 함수를 호출하는 법을 진행해 보겠다.

 

 

 

그림 참조: https://trufflesuite.com/blog/a-sweet-upgradeable-contract-experience-with-openzeppelin-and-truffle/
반응형