티스토리 뷰

반응형

hardhat으로 contract를 작성하고 배포해보겠다.

이 과정을 수행하기 위해선 반드시 사전에 환경설정이 완료되어야한다.

환경설정이 필요하다면 아래 링크를 참고해서 세팅해주자.

 

[Ethereum] hardhat 설치 및 환경설정

hardhat은 이더리움 소프트웨어 개발환경으로 스마트 컨트랙트와 DApp을 개발, 컴파일, 디버깅, 배포하기위한 완전한 개발환경을 제공한다. hardhat은 반복된 작업(like 검증 과정)을 간단한 명령어 한

jerryjerryjerry.tistory.com


Solidity 코드 작성

아래 사진은 이번 프로젝트 실행에 필요한 디렉토리 목록이다.

우선 프로젝트 경로 아래에 contracts라는 폴더를 만든 후 Box.sol 파일을 생성해준다.

Box.sol을 작성해서 기본적인 getter, setter를 제공하는 컨트랙트를 배포해 볼 것이다.

// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
 
contract Box {
    uint256 private value;
 
    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);
 
    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }
 
    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }
}

Box라는 contract의 상태를 초기화할 수 있는 것은 생성자가 아니라 public 접근자로 모두 접근할 수 있는 store 함수이다.


Solidity 코드 테스트 및 컴파일

작성한 코드를 로컬에서 실행시키고 테스트하기 위해선 패키지를 하나더 설치해줘야한다.

npm install --save-dev chai

작성한 Box.sol이 컴파일이 되는지 올바르게 작성됐는지

proxy를 통해 호출될 수 있는지 모듈 테스트를 진행할 것이다.

 

test라는 폴더를 생성한 뒤 아래 Box.js 와 Box.proxy.js 파일을 생성하고 아래처럼 작성하자

// test/Box.js
// Load dependencies
const { expect } = require('chai');
 
let Box;
let box;
 
// Start test block
describe('Box', function () {
  beforeEach(async function () {
    Box = await ethers.getContractFactory("Box");
    box = await Box.deploy();
    await box.deployed();
  });
 
  // Test case
  it('retrieve returns a value previously stored', async function () {
    // Store a value
    await box.store(42);
 
    // Test if the returned value is the same one
    // Note that we need to use strings to compare the 256 bit integers
    expect((await box.retrieve()).toString()).to.equal('42');
  });
});
// test/Box.proxy.js
// Load dependencies
const { expect } = require('chai');
 
let Box;
let box;
 
// Start test block
describe('Box (proxy)', function () {
  beforeEach(async function () {
    Box = await ethers.getContractFactory("Box");
    box = await upgrades.deployProxy(Box, [42], {initializer: 'store'});
  });
 
  // Test case
  it('retrieve returns a value previously initialized', async function () {
    // Test if the returned value is the same one
    // Note that we need to use strings to compare the 256 bit integers
    expect((await box.retrieve()).toString()).to.equal('42');
  });
});

그런 다음 테스트를 실행하기 위해 아래 명령어를 입력하자

npx hardhat test
$ npx hardhat test
✔ Help us improve Hardhat with anonymous crash reports & basic usage data? (Y/n) · y
Downloading compiler 0.8.9
Compiled 1 Solidity file successfully


  Box
    ✔ retrieve returns a value previously stored

  Box (proxy)
    ✔ retrieve returns a value previously initialized


  2 passing (2s)

에러 발생없이 Box 컨트랙트와 proxy가 잘 컴파일 됐다면 컨트랙트를 배포할 준비가 완료된 것이다.


contract 배포

Box를 배포하기 위해 배포 코드를 작성해 줄 것이다.

scripts 라는 폴더를 생성하고 아래에 deploy.js를 만들어 아래와 같이 작성해주자.

Box 컨트랙트를 인스턴스화하고, Box의 상태값을 초기화하는 함수는 store 함수, 그리고 초기값은 42를 넣는다.

// scripts/deploy.js
async function main() {
    const Box = await ethers.getContractFactory("Box");
    console.log("Deploying Box...");
    const box = await upgrades.deployProxy(Box, [42], { initializer: 'store' });
    console.log("Box deployed to:", box.address);
  }
  
  main()
    .then(() => process.exit(0))
    .catch(error => {
      console.error(error);
      process.exit(1);
    });

그리고 앞에서 설정해준 .env 파일에 네트워크 정보를 hardhat.config.js에 입력해준다.

// hardhat.config.js
require("@nomiclabs/hardhat-ethers");
require('@openzeppelin/hardhat-upgrades');
require("dotenv").config();

const { ALCHEMY_URL, METAMASK_KEY } = process.env;

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

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


/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.9",
  defaultNetwork: "goerli",
  networks: {
    hardhat: {},
    goerli: {
      url: ALCHEMY_URL,
      accounts: [`0x${METAMASK_KEY}`]
    }
  }
};

 

드디어 작성한 Box 컨트랙트를 배포할 차례이다.

터미널에 아래와 같은 명령어를 입력해준다(프로젝트의 루트 경로에서 입력해주자)

npx hardhat run --network goerli scripts/deploy.js

배포에 성공하면 아래와 같이 주소 하나가 로그로 뜨는데

배포한 컨트랙트의 주소이므로 잘 복사해두자

$ npx hardhat run --network goerli scripts/deploy.js
Deploying Box...
Box deployed to: 0x249bEb20558DBaBec3c0b78636F839D04cC00000

contract 다루기

터미널을 하나 더 열고 콘솔을 열어 컨트랙트를 다뤄보자.

goerli 네트워크로 연결하는 명령어이다.

npx hardhat console --network goerli

그런 다음 Box 컨트랙트에 접근할 수 있는 인스턴스(proxy 역할)를 만들어 주자.

배포할 당시 Box.js에 초기값을 42를 넣어줬다.

retrieve 함수를 출력해보면 초기값 42가 나타나는 것을 확인할 수 있다.

$ npx hardhat console --network goerli
Welcome to Node.js v14.19.0.
Type ".help" for more information.
> const Box = await ethers.getContractFactory("Box")
undefined
> const box = await Box.attach("0x249bEb20558DBaBec3c0b78636F839D04cC00000")
undefined
> (await box.retrieve()).toString()
'42'

Box 컨트랙트에는 value 값을 초기화하는 store 함수도 있었다.

store 함수로 value 값을 재정의 해보자.

> await box.store(12)
{
  hash: '0xcfe64a4cf4c60624e0e5482399544920de371459222d4d1750ba67c0c1500000',
  type: 2,
  accessList: [],
  blockHash: null,
  blockNumber: null,
  transactionIndex: null,
  confirmations: 0,
  from: '0xaCb8D2dD46887C9304db1BaB6633b4fC43400000',
  gasPrice: BigNumber { value: "1500000011" },
  maxPriorityFeePerGas: BigNumber { value: "1500000000" },
  maxFeePerGas: BigNumber { value: "1500000011" },
  gasLimit: BigNumber { value: "35209" },
  to: '0x249bEb20558DBaBec3c0b78636F839D04cC00000',
  value: BigNumber { value: "0" },
  nonce: 7,
  data: '0x6057361d000000000000000000000000000000000000000000000000000000000000000c',
  r: '0x7f0bd5486479cd0abb7a54cbc436673a417a2a4a635bff9016c8a024f3091f0c',
  s: '0x7af04f54e65f03e8a66f4ab38f97ada9cf6b08b87bbe342a471cad8314fdeb27',
  v: 0,
  creates: null,
  chainId: 5,
  wait: [Function (anonymous)]
}
> (await box.retrieve()).toString()
'12'

 

 

store 함수는 call 함수가 아니고 상태를 변경하는 setter 함수이기에

실행시키면 반드시 트랜잭션을 일으킨다.

시간이 좀 걸리면서 위와 같이 hash와 트랜잭션 recipt 정보가 정상적으로 떴다면 올바르게 처리된 것이다.

그런다음 다시 retrieve 함수를 출력하면 상태값이 12로 잘 변경된 것이 보인다.

 

goerli 이더스캔에서도 확인하면 성공적으로 트랜잭션이 보내진 것을 확인할 수 있다.

 


여기까지 hardhat으로 간단한 컨트랙트 작성과 배포, 실행까지 해보았다.

더 많은 기능이 있는 컨트랙트를 작성하고 알맞은 proxy 코드를 만들 수 있다면

더 좋은 플랫폼 개발을 할 수 있을 것이다.

반응형
댓글
공지사항