스마트 컨트랙트 배포하기 With Truffle And Hardhat
2022.07.11
Truffle
1. Truffle 설치 및 사용
1.1. 설치
npm install truffle -g
npm install -g ganache-cli
ganache : 로컬에서 블록체인 테스트하는 기능
1.2. 트러플 구성
truffle init
- 실행 결과
contracts/
: solidity contract 디렉터리migrations/
: 스크립팅 가능한 배포 파일 디렉터리test/
: 애플리케이션 및 계약을 테스트 하기 위한 테스트 파일 디렉터리truffle.js
: 트러플 구성 파일
1.3. 실습용 예제 사용하기
truffle에서 기본 제공하는 예제 파일
https://trufflesuite.com/boxes/metacoin/
truffle unbox metacoin
- 실행 결과
1.4. 테스트
truffle test // 전체 테스트
truffle test ./test/TestMetaCoin.sol // 개별 파일 테스트
- 실행 결과
1.5. 컨트랙트 컴파일
truffle compile
- 실행 결과
1.6. 로컬 체인에서 테스트
→ 로컬에서 돌아가는 테스트용 블록체인
truffle develop
-
실행 결과
-
Truffle Develop 프롬프트에서는 truffle 접두사를 생략한 truffle 명령 실행 가능 ex) truffle compile → compile
- compile (truffle compile)
- migrate (truffle migrate : 컴파일된 계약을 블록체인에 배포하는 명령)
2. Ganache - 가나슈
- 로컬 네트워크를 구성하여 배포 하기
- Ganache로 실제 블록체인과 같이 로컬에서 네트워크를 구성하여 배포 테스트 진행
실제 ethereum과 똑같이 생성할수 있는 geth같은 경우 트랜잭션을 실행하는데에 15초씩 걸리기 때문에 개발 속도가 느려진다. → 가나슈를 사용하면 실제 네트워크와 같은 환경에서 바로바로 결과를 볼 수 있다.
2.1. truffle-config.js
수정
module.exports = {
// 네트워크 설정 (로컬, 테스트넷, 메인넷, ... 등등을 설정 한다.)
networks: {
dev: {
// 이름은 아무거나 사용 가능
host: "127.0.0.1", // Localhost
port: 8545, // Standard Ethereum port
network_id: "*", // Any network
},
},
// 컴파일러 설정
compilers: {
solc: {
version: "0.8.13", // 컴파일러 버전
},
},
};
- 로컬 체인에 배포하기 위한 설정
2.2. Ganache-cli 실행
ganache-cli
- 테스트를 위한 100ETH를 가진 계정 10개를 자동 생성
- 8545포트를 기본으로 사용
- 실행 결과
2.3. 컴파일
truffle compile
- truffle compile (or mirgrate시 자동 컴파일 된다.)
2.4. 배포
(ganache-cli가 실행 중이여야함)
- 실행 결과 - 테스트
- 실행 결과 - 배포
- 실행 결과 - 배포시 ganache-cli
3. 실제 네트워크에 배포
-
./migrations/deploy
const A = artifacts.require("[계약명]"); const B = artifacts.require("[계약명]"); // 계약명은 일치해야한다. module.exports = function (deployer) { deployer.deploy(A); deployer.link(A, B); // 이미 배포된 라이브러리를 연결한다. deployer.deploy(B); };
-
truffle-config.js
require("dotenv").config(); const privateKey = process.env["PRIVATE_KEY"]; const infuraProjectId = process.env["INFURA_PROJECT_ID"]; const HDWalletProvider = require("@truffle/hdwallet-provider"); module.exports = { compilers: { solc: { version: "0.8.13", }, }, networks: { goerli: { provider: () => new HDWalletProvider( privateKey, `https://goerli.infura.io/v3/${infuraProjectId}` ), network_id: 5, // Goerli's id chain_id: 5, }, }, };
-
배포
truffle migrate --network goerli
Hardhat
1. 설치
npm install --save-dev hardhat
npx hardhat
- 실행 결과
→ create a JavaScript project 선택
2. 필요 모듈 설치
npm install --save-dev @nomicfoundation/hardhat-toolbox hardhat-abi-exporter
require("@nomicfoundation/hardhat-toolbox");
require("hardhat-abi-exporter");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.9",
network: {},
abiExporter: {
path: "./ABI",
runOnCompile: true,
clear: true,
},
};
3. contract 작성 및 컴파일
-
테스트용 예시 컨트랙트
//SPDX-License-Identifier: UNLICENSED // 솔리디티 파일은 pragma로 시작됩니다. // 컴파일러는 이 부분을 보고 버전을 검증하게 됩니다. pragma solidity ^0.8.9; // 여기서부터 스마트 컨트랙트를 위한 주된 부분입니다. contract Token { // Some string type variables to identify the token. string public name = "My Hardhat Token"; string public symbol = "MHT"; // 고정된 양의 토큰 수 uint256 public totalSupply = 1000000; // 이더리움 계정을 저장하기 위해 address 타입의 변수를 선언합니다. // mapping은 key/value 맵 형태의 자료구조 이며 여기에 각 계정의 잔고를 기록합니다. mapping(address => uint256) balances; // Transfer 이벤트는 오프체인 애플리케이션에서 스마트 컨트랙트에서 어떤 일이 일어났는지 이해하는 것을 도와줍니다. event Transfer(address indexed _from, address indexed _to, uint256 _value); /** * 컨트랙트 초기화 */ constructor() { // totalSupply는 트랜잭션을 만든 사람의 계정에 주어집니다. balances[msg.sender] = totalSupply; address owner = msg.sender; } /** * 토큰을 전송하기 위한 함수 * * `external` modifier는 해당 함수가 외부에서만 호출 가능하도록 접근 제어 해줍니다. */ function transfer(address to, uint256 amount) external { // 트랜잭션을 보내는 사람이 적당한 토큰을 가지고 있는지 확인합니다. // 만약 require에서 첫 번째 인자가 "false"일 경우에는 해당 동작이 실패하고 // 트랜잭션은 초기화됩니다. require(balances[msg.sender] >= amount, "Not enough tokens"); balances[msg.sender] -= amount; balances[to] += amount; // 오프체인 애플리케이션에 해당 내용을 전달해줍니다. emit Transfer(msg.sender, to, amount); } /** * 주어진 계정의 토큰 잔고를 읽기 위한 읽기 전용 함수 * view는 해당 함수의 상태를 변경시키지 않고 단순히 읽기만 하겠다는 의미이며 * 그렇기 때문에 가스비가 발생하지 않는다. */ function balanceOf(address account) external view returns (uint256) { return balances[account]; } }
-
contract 작성시 버전 에러 해결
vsCode extenstion solidity 설치
다음 solidity: Change the default … → remote 설정
-
컨트랙트 컴파일
npx hardhat compile
- 실행 결과
abi를 외부로 빼는 모듈은
hardhat-abi-exporter
를 세팅해서 이다.
- 실행 결과
abi를 외부로 빼는 모듈은
4. 컨트랙트 테스트
- 테스트 예제 코드
const { expect } = require("chai"); describe("Token contract", function () { it("Deployment should assign the total supply of tokens to the owner", async function () { const [owner] = await ethers.getSigners(); const Token = await ethers.getContractFactory("Token"); const hardhatToken = await Token.deploy(); const ownerBalance = await hardhatToken.balanceOf(owner.address); expect(await hardhatToken.totalSupply()).to.equal(ownerBalance); }); });
- 테스트 코드 실행
npx hardhat test
- 실행 결과
- 테스트 코드 실행
5. 네트워크에 배포
-
./script/deploy.js
const { expect } = require("chai"); describe("Token contract", function () { it("Deployment should assign the total supply of tokens to the owner", async function () { const [owner] = await ethers.getSigners(); const Token = await ethers.getContractFactory("Token"); const hardhatToken = await Token.deploy(); const ownerBalance = await hardhatToken.balanceOf(owner.address); expect(await hardhatToken.totalSupply()).to.equal(ownerBalance); }); });
-
./hardhat.config.js
네트워크에 설정
const ALCHEMY_API_KEY = "KEY"; const GOERLI_PRIVATE_KEY = "YOUR GOERLI PRIVATE KEY"; module.exports = { solidity: "0.8.9", networks: { goerli: { url: `https://eth-goerli.alchemyapi.io/v2/${ALCHEMY_API_KEY}`, accounts: [GOERLI_PRIVATE_KEY], }, }, };
-
배포
npx hardhat run scripts/deploy.js --network <network-name>