티스토리 뷰
hardhat으로 contract를 작성하고 배포해보겠다.
이 과정을 수행하기 위해선 반드시 사전에 환경설정이 완료되어야한다.
환경설정이 필요하다면 아래 링크를 참고해서 세팅해주자.
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 코드를 만들 수 있다면
더 좋은 플랫폼 개발을 할 수 있을 것이다.
'BlockChain > Ethereum' 카테고리의 다른 글
[Ethereum] 알케미(Alchemy) API Key 발급받는 방법 (0) | 2022.08.11 |
---|---|
[Ethereum] Goerli 테스트넷 Faucet 이더 얻기 (1) | 2022.08.11 |
[Ethereum] hardhat 설치 및 환경설정 (0) | 2022.08.10 |
[Ethereum] 이더스캔(Etherscan) 보는 방법, 개발 용어 정리 (2) (0) | 2022.06.14 |
[Ethereum] EIP-1559 수수료 모델에 대하여 (0) | 2022.06.13 |