Hardhat으로 업그레이드 가능한 견고성 계약 생성

스마트 계약은 불변의 의미로 일단 블록체인에 배포되면 더 이상 편집할 수 없습니다. 이것은 좋은 일이지만 배포된 계약에 버그가 있거나 새 계약을 배포하지 않고 이미 배포된 스마트 계약에 특정 기능을 추가해야 하는 경우 이전에 배포된 계약의 상태를 잃어버릴 수 있습니다.

OpenZeppelin은 상태 손실 없이 스마트 계약을 업데이트할 수 있는 업그레이드 가능 항목을 제공하여 구출합니다. 이 튜토리얼은 Hardhat 및 OpenZeppelin 업그레이드 가능 계약을 사용합니다.

터미널을 열고 다음을 입력하여 새 npm 프로젝트를 만듭니다. (이 튜토리얼에서는 사용자가 이미 노드와 npm을 시스템에 설치했다고 가정합니다.)

튜토리얼 코드가 살아 있음here

  • Project Dependencies
  • Project
  • How Upgradable Contract Works
  • Verify Upgradable Contract
  • Upgrading a smart contract
  • Things to know when working with Upgradable Contracts

  • 프로젝트 종속성

    npm init --y
    

    Open the package.json file that was created when the above command was run and add the following dependencies code below to the package.json file

      "devDependencies": {
        "@nomiclabs/hardhat-ethers": "^2.0.3",
        "@nomiclabs/hardhat-etherscan": "^2.1.8",
        "@openzeppelin/hardhat-upgrades": "^1.12.0",
        "ethers": "^5.5.2",
        "hardhat": "^2.8.0"
      },
      "dependencies": {
        "dotenv": "^16.0.0"
      }
    

    Install the above dependencies by running npm i . This installs the dependencies into the project. @openzeppelin/hardhat-upgrades provides functionality for creating and deploying upgradable contracts. @nomiclabs/hardhat-etherscan is used for verifying the contract using Etherscan. @nomiclabs/hardhat-ethers allows hardhat to work with ether.js.

    프로젝트

    Create a new hardhat project by running in the terminal:

     npx hardhat
    

    This presents us options to select a project template. Select the first option which is Create a sample project and this creates a sample project with boiler plate code.

    Create a .env file in the project directory. This file will contain our environment variable. In this project, we will need values for the following environmental variables which are:
    INFURA_API_KEY
    PRI_KEY
    ETHERSCAN_API_KEY
    INFURA_API_KEY : our API key we get from
    Infura .Infura에 연결하려면 이것이 필요합니다.PRI_KEY : 메타 마스크에 있는 계정의 기본 키입니다. 트랜잭션 서명에 사용됩니다.ETHERSCAN_API_KEY : Etherscan의 API 키입니다. 이것은 계약을 확인하는 데 사용됩니다.
    hardhat-config.js 파일을 열고 아래 코드를 추가하여 구성합니다.

    require("@nomiclabs/hardhat-ethers");
    require("@openzeppelin/hardhat-upgrades");
    require("@nomiclabs/hardhat-etherscan");
    
    require('dotenv').config();
    module.exports = {
      solidity: "0.8.10",
      networks: {
        ropsten: {
          url: `https://ropsten.infura.io/v3/${process.env.INFURA_API_KEY}`,
          accounts: [process.env.PRI_KEY],
        },
        rinkeby: {
          url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`,
          accounts: [process.env.PRI_KEY]
        }
      },
      etherscan: {
        apiKey: process.env.ETHERSCAN_API_KEY,
      },
    };
    


    프로젝트에서 계약 폴더를 열고 Greeter.sol 파일을 삭제합니다. CalculatorV1.sol라는 새 파일을 만듭니다. 여기에는 rinkeby 네트워크에 배포할 스마트 계약이 포함됩니다.

    파일CalculatorV1.sol 내에서 아래 코드로 바꿉니다.

    pragma solidity 0.8.10;
    
    import "hardhat/console.sol";
    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    
    contract CalculatorV1 is Initializable {
       uint public val;
       function initialize(uint256 _val ) external initializer{
            val = _val;
        }
        function add(uint a, uint b) public pure returns (uint) {
            return a + b;
        }
       function getVal() public view returns (uint) {
            return val;
        }
    }
    


    이 스마트 계약은 계산기의 간단한 계약입니다. 계약은 Openzeppelin 계약인 Initializable 계약에서 상속됩니다. initialize 함수가 한 번만 호출되도록 합니다. 업그레이드 가능한 계약에는 생성자가 없으므로 initialize 함수가 생성자로 작동하고 한 번만 호출해야 합니다. initializer 수정자는 함수가 한 번 호출되도록 합니다.

    계약에는 val라는 이름의 공용 변수와 initialize , addgetVal 의 세 가지 함수가 있습니다. hardhat-config.js 파일에서 설정한 Rinkeby 네트워크에 이 계약을 배포하려고 합니다.

    scripts 폴더 안에 새 파일을 만들고 이름을 지정합니다deploy_contract.js . 이 파일에는 계산기 계약을 배포할 코드가 포함됩니다.
    deploy_contract.js 파일 안에 다음 코드를 추가합니다.

    //scripts/deploy_contract.js
    const { ethers, upgrades } = require("hardhat");
    
    async function main() {
       const CalculatorV1 = await ethers.getContractFactory("CalculatorV1");
        console.log("Deploying Calculator...");
        const calculator = await upgrades.deployProxy(CalculatorV1, [42], {
            initializer: "initialize",
        });
        await calculator.deployed();
        console.log("Calculator deployed to:", calculator.address);
    }
    
    main();
    


    위의 코드는 ethers 에서 upgradeshardhat 가 필요합니다. async 함수가 생성되고 함수 내에서 계약 이름( CalculatorV1 )과 함께 ethers를 사용하여 계약 팩토리가 검색됩니다. upgrades.deployProxy는 계약 팩터리에서 전달되는 계약과 전달된 매개 변수가 있는 초기화 함수를 배포하는 데 사용됩니다.

    계약에서 initialize 변수의 값을 설정하는 val 함수가 있음을 기억하십시오. 이 함수는 함수에 대한 매개 변수로 값42을 전달하는 계약이 배포될 때 호출됩니다.

    터미널에서 다음 코드를 실행하여 계약을 배포합니다.

     npx hardhat run --network rinkeby scripts/deploy_contract.js
    


    몇 초 후 계약이 배포됩니다.
    콘솔에 기록된 계약 주소.

    업그레이드 가능한 계약 작동 방식

    When we deployed the contract, three contracts were deployed in total. These are a Proxy contract, a Proxy Admin contract and the Implementation contract which is our CalculatorV1 . When a user interacts with the contract, he is actually interacting with the Proxy contract. The Proxy contract makes a delegateCall to our CalculatorV1 contract. For example A contract named A makes a delegateCall to a contract B calling a function in contract B . The function in B is executed in the state of variable A .

    For our upgradable contract, the Proxy contract calls the Implementation contract (CalculatorV1). The state change is made on the Proxy contract. The Proxy Admin contract is used for updating the address of the implementation contract
    inside the Proxy contract.

    업그레이드 가능한 계약 확인

    When we deployed our contract, we got back the address of the Proxy contract. if we search for this address on Ether scan we are presented with a contract with name
    TransparentUpgradeableProxy
    . This contract is the Proxy contract and this will be responsible for calling the Implementation contract.

    To verify the Implementation contract and publish the contract code we have to look into the project folder and you will see a folder named .openZeppelin . Open the folder and you will find a file named rinkeby.json . This file is so named because of the network we deployed the contract to. This file was auto generated by hardhat when we ran the deployed script. Inside this file the addressees of the Implementation contract, Proxy admin and the Proxy are kept. As the contract is updated the new addresses are added to the file. Copy the address of the
    Implementation contract and proceed to the terminal for verification.
    Run this code at the terminal:
    npx hardhat verify --network rinkeby contractAddress

    Replace contract address with the Implementation address that was copied and run the code. This verifies the contract source code. We also need to verify the Proxy admin contract. Go to Etherscan and search for the Proxy contract using its address. Click on the Contract tab then click on Code tab and click on the more options. Click on is this a proxy? and then click on verify. The Proxy contract will be verified.

    Upgrading a Contract

    Create a new file inside the contract folder and name it CalculatorV2 .

    pragma solidity 0.8.10;
    
    import "hardhat/console.sol";
    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    
    contract CalculatorV2 is Initializable {
        uint public val;
    
        function add(uint a, uint b) public pure returns (uint) {
            return a + b;
        }
    
        function multiply(uint a, uint b) public pure returns (uint) {
            return a * b;
        }
      function getVal() public view returns (uint) {
            return val;
        }
    
    }
    

    We have added a new function to this version of the contract. The multiply function is added. To upgrade the deployed contract. Create a script inside the scripts folder and create a file upgrade_contract.js . Inside this file put the following code.

    const { ethers, upgrades } = require("hardhat");
    //the address of the deployed proxy
    const PROXY = "0xaf03a6F46Eea7386F3E5481a4756efC678a624e6";
    
    async function main() {
        const CalculatorV2 = await ethers.getContractFactory("CalculatorV2");
        console.log("Upgrading Calculator...");
        await upgrades.upgradeProxy(PROXY, CalculatorV2);
        console.log("Calculator upgraded");
    }
    
    main();
    

    The address of the implementation Proxy and the contract factory of the new version of the contract is passed as parameters to upgrades.upgradeProxy . Run the code by typing on the terminal :

     npx hardhat run --network rinkeby scripts/upgrade_contract.js
    

    This will update the address of the Implementation contract in the Proxy contract to make use of the new version deployed. Run the getVal contract to retrieve the value of the state variable val . You will notice that the value of val is still the value we initiated it to be when we deployed the first version of the contract. That is the beauty of upgradable contracts which is the preservation of variable state.

    To verify the contract, we have to perform the same steps that was used to verify the first version of the contract.

    업그레이드 가능한 계약으로 작업할 때 알아야 할 사항

    When working with Upgradable contracts the following points should be noted:

    • Constructor: An upgradable contract can not have a constructor . If you have code that must run when the contract is created. The code should be placed in an init function that will get called when the contract is deployed. Openaeppelin Initializable can be used to ensure a function is called once. ( initializer )
    function initialize(uint256 _val ) external initializer {
            val = _val;
    }
    

    The initialize function will be called only once because of the initializer modifier attached to it.

    • state variables : state variables in upgradable contracts once declared cannot be removed. Assuming we have a version one contract where we define the following state variables :
    uint public val;
    string public name;
    

    When deploying version two of the contract, we must ensure that version two of the contract upgrade also contain the same variable as version one in the same order as was defined in version one. The order of the variable matters. if we want to use new state variables, they are added at the bottom.

    uint public val;
    string public name;
    string public newVariableOne;
    uint public newVariableTwo;
    
    • variable initialization : only state variable declared as const and immutable can be initialize. This is because initializing a state variable will attempt to create a storage for that variable. And as we know the Implementation contract don't use its state. The Proxy contract provides the storage used by the Implementation contract.

    The value of variables declared as const are placed in the application code of the contract instead of in storage. That's why only const variable can be initialize.

    • Implementation contract can not contain code that will self destruct the contract. If a contract is self destruct and removed from the blockchain, the Proxy contract will no longer know where to look to execute functions.
    function kill() external {
         selfdestruct(payable(address(0)));
    }
    

    요약



    스마트 계약을 업그레이드하는 방법이 있으면 계약 코드를 변경하고 개선해야 할 때 유용할 수 있습니다. 읽어 주셔서 감사합니다...

    좋은 웹페이지 즐겨찾기