본문 바로가기

블록체인

[블록체인] 블록체인 이더리움 Dapp 개발에 하드햇과 오픈제펠린 활용하기

하드햇(Hardhat)

트러플과 유사한 이더리움 기반 스마트 컨트랙트 개발 도구

자바스크립트 기반으로 자유도가 높고 유연한 개발 환경을 제공

스마트 컨트랙트에서 console.log를 사용하여 값을 출력할 수 있는 기능도 제공

Ganache같은 가상 이더리움 제공

web3.js대신 ethers.js 사용

오픈제펠린의 Upgrades

수정 가능한 "Upgradable" 컨트랙트를 쉽게 작성하고 배포할 수 있도록 개발도구와 함께 쓸 수 있는 플러그인

트러플과 하드햇에서 사용할 수 있는 플러그인

하드햇 설치

글로벌 패키지로 설치하지 않음 (-g)

yarn, npx 사용

cd mydapp
yarn init -y
yarn add hardhat --dev
npx hardhat help

하드햇 프로젝트 폴더

npx hardhat // "sample project" 선택
mydapp
-- artifacts // 컴파일 결과물
-- cache
-- contracts // 컨트랙트
-- scripts // 배포 등의 실행 스크립트
-- test // 단위테스트
-hardhat-config.js // 개발환경 설정

하드햇 플러그인

모든 작업들이 플러그인 기반으로 이루어짐

기본적으로 설치해야 할 플러그인 🔽

yarn add
			// 단위테스트 플러그인
			@nomiclabs/hardhat-waffle
			ethereum-waffle
			chai
			// ethers.js 이더리움 웹 라이브러리
			@nomiclabs/hardhat-ethers
			ethers
			--dev

// 오픈제펠린 Upgradable 컨트랙트 플러그인
yarn add @openzeppelin/hardhat-upgrades --dev

하드햇 명령어 확인

npx hardhat help

하드햇 주요 Task(명령어)

npx hardhat compile
npx hardhat test --netwowrk rinkeby ./test/mytest.js
npx hardhat run --network rinkeby ./scripts/mydeploy.js
npx hardhat console --network rinkeby

hardhat.config.js

accounts라는 task 정의

// require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-waffle");

task("accounts", "Prints the list of accounts", async () => {
  const accounts = await ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address)
  }
})

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.15",
};

*업그레이더블 컨트랙트는 constructor 생성자 함수를 쓰지 않는다.

accounts task 실행

npx hardhat accounts // 계정확인

SimpleStorageUpgrade.sol

//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;

import "hardhat/console.sol";

contract SimpleStorageUpgrade {

    uint storedData;

    event Change(string message, uint newVal);

    function set(uint x) public {
        // console.log("The value is %d", x);
        require(x < 5000, "Should be less than 5000");
        storedData = x;
        emit Change("set", x);
    }

    function get() public view returns (uint) {
        return storedData;
    }
}

단위 테스트

SimpleStorageUpgrade.test.js

const hre = require("hardhat");
const {exprect, expect} = require("chai");

describe ("SimpleStorageUpgrade", function() {

    const wallets = waffle.provider.getWallets();

    before(async () => {
        const signer = waffle.provider.getSigner();
        const SimpleStorageUpgrade = await hre.artifacts.readArtifact("SimpleStorageUpgrade");
        this.instance = await waffle.deployContract(signer, SimpleStorageUpgrade);
    });

    it("should change the value", async () => {
        const tx = await this.instance.connect(wallets[1]).set(500);
        const v = await this.instance.get();
        expect(v).to.be.equal(500);
    });

    // revert reason
    it("sould revert", async() => {
        await expect(this.instance.set(6000))
            .to.be.revertedWith("Should be less than 5000");
    });
})
npx hardhat test ./test/SimpleStorageUpgrade.test.js

오픈제펠린 업그레이드 플러그인 추가

  1. hardhat-config.js에 오픈제펠린 업그레이드 플러그인 추가
require("@openzeppelin/hardhat-upgrades");
  1. 배포 스크립트 작성 (SimpleStorageUpgrades.deploy.js)
const hre = require("hardhat");

async function main() {
    
    //TODO
    const SimpleStorageUpgrade = await hre.ethers.getContractFactory("SimpleStorageUpgrade")
    const ssu = await upgrades.deployProxyy(SimpleStorageUpgrade, [500], { initializer: 'set' }); // 초기값 지정

    console.log("SimpleStorageUpgrade deployed to:", ssu.address);
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
  1. upgrades.deployProxy를 사용하여 배포
npx hardhat node // 가나슈처럼
npx hardhat run --network localhost .\\scripts\\SimpleStorageUpgrades.deploy.js

노드 콘솔에서

> const f = await ethers.getContractFactory("SimpleStorageUpgrade") // 컨트랙트 인스턴스 생성
> const ssu = await f.attach("0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0") // 배포 주소에 attach
> ssu.address
> (await ssu.get()).toString() // '500' 출력
> let tx = await ssu.set(1000) // 값 변경
> (await ssu.get()).toString() // '1000' 출력

컨트랙트 내용 바꾸기

SimpleStorageUpgradeV2.sol

//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;

import "hardhat/console.sol";

contract SimpleStorageUpgrade {

    uint storedData;
    uint storedKey;

    event Change(string message, uint newVal);

    function set(uint x) public {
        // console.log("The value is %d", x);
        require(x < 10000, "Should be less than 5000");
        storedData = x;
        emit Change("set", x);
    }

    function get() public view returns (uint) {
        return storedData;
    }

    function setKey(uint key) public {
        storedKey = key;
    }

    function getKey() public view returns (uint) {
        return storedKey;
    }
}

SimpleStorageUpgradesV2.deploy.js

const hre = require("hardhat");

async function main() {
    
    const proxyAddress = "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0";
    const SimpleStorageUpgradeV2 = await hre.ethers.getContractFactory("SimpleStorageUpgradeV2");
    const ssu2 = await upgrades.upgradeProxy(proxyAddress, SimpleStorageUpgradeV2); // 초기값 지정

}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});