[ethereum] truffle을 이용하여 스마트 컨트랙트 개발하기
ruffle을 이용하여 스마트 컨트랙트를 개발하는 방법을 다룹니다.
● 셋업
$ npm install -g truffle
· 프로젝트 생성
$ mkdir dapp
$ cd dapp
$ truffle init
init 명령어를 사용하여 truffle 기반의 프로젝트를 생성할 수 있습니다.
$ tree
.
├── contracts
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
└── truffle-config.js
3 directories, 3 files
▶ contracts
solidity 코드 관리
▶ migrations
스마트 컨트랙트 배포 관리
▶ test
테스트 코드 작성
▶ build
build는 컴파일 결과를 관리합니다.
프로젝트 생성시에는 없지만 compile을 하면 build/contracts 아래에 metadata.json를 생성합니다.
$ truffle compile
▶truffle-config.js
네트워크 연결정보 및 solidity compiler 버전 정보 등을 관리합니다.
/**
* trufflesuite.com/docs/advanced/configuration
*/
// const HDWalletProvider = require('@truffle/hdwallet-provider');
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();
module.exports = {
networks: {
dev: {
host: "127.0.0.1",
port: 8545,
network_id: "*",
},
},
mocha: {},
compilers: {
solc: {
version: "0.8.10",
}
},
};
network는 더 많은 정보를 넣을 수 있습니다. 이외에 더 많은 정보를 설정할 수 있습니다.
https://trufflesuite.com/docs/truffle/reference/configuration
● 개발 프로세스
일반적으로 개발이라하면 다음과 같은 프로세스에 맞춰서 개발합니다.
1. 소스코드 작성
2. 컴파일 & 빌드
3. 배포
4. 개별적으로 실행해보기
5. 테스트 하기
truffle은 이런 일련의 과정을 코드 및 명령어 기반으로 수행할 수 있습니다.
· 소스코드 작성
소스코드는 contracts 아래에서 관리합니다.
파일명: contracts/Mung.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
contract Mung {
string public text;
constructor(string memory _text) {
text = _text;
}
function setText(string memory _text) public{
text = _text;
}
function errorOccur(uint a) public pure returns (uint) {
require(a != 0, "hello world error");
return a;
}
}
· 컴파일 및 빌드
$ truffle compile
Compiling your contracts...
===========================
> Compiling ./contracts/HelloWorld.sol
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/Mung.sol
> Artifacts written to /Users/jeongtaepark/Desktop/ethereum-truffle-sample/build/contracts
> Compiled successfully using:
- solc: 0.8.10+commit.fc410830.Emscripten.clang
빌드를 성공적으로 완료하면 build/contracts/*.json를 생성합니다.
· 스마트컨트랙트 배포
truffle은 migrate 명령어만으로 아주 쉽게 배포를 할 수 있습니다.
배포에 대한 코드는 migrations 아래에서 관리합니다. 이 코드는 생성자 전달할 인자를 정의합니다.
파일명: migrations/2_Mung.js
여기서 한가지 중요한 점은 migrations는 다른 디렉터리와 다르게 "숫자_"를 prefix해야 합니다.
const Mung = artifacts.require("Mung");
module.exports = function (deployer) {
deployer.deploy(Mung, "Hello Mung~");
};
$ truffle migrate --network [truffle-config에 명시한 network]
여기선 dev로 노드 정보를 작성했기 때문에 --network dev를 전달합니다.
$ truffle migrate --network dev
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
> Something went wrong while attempting to connect to the network at http://127.0.0.1:8545. Check your network configuration.
Could not connect to your Ethereum client with the following parameters:
- host > 127.0.0.1
- port > 8545
- network_id > *
Please check that your Ethereum client:
- is running
- is accepting RPC connections (i.e., "--rpc" or "--http" option is used in geth)
- is accessible over the network
- is properly configured in your Truffle configuration file (truffle-config.js)
Truffle v5.4.22 (core: 5.4.22)
Node v14.17.3
앗! 에러가!!!
할 시간에 에러를 읽으세요 제발좀...
해당 에러는 dev로 정의한 노드를 연결할 수 없다는 에러입니다. 당연히 연결을 못하죠. 노드가 로컬에 없으니
$ npm install -g ganache-cli
$ ganache-cli
ganache-cli를 이용하여 가상의 네트워크를 만듭니다.
다시 migrate하면 정상적으로 스마트 컨트랙트를 배포할 수 있습니다.
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Starting migrations...
======================
> Network name: 'dev'
> Network id: 1637999164665
> Block gas limit: 6721975 (0x6691b7)
2_Mung.js
=========
Deploying 'Mung'
----------------
> transaction hash: 0xb390ac6cc6cfe4b52220d0388e5b595fd1b79696de1ed1ced5b6fe373a6347ab
> Blocks: 0 Seconds: 0
> contract address: 0x680d78685dE52F50201bC4e5b1723Daf44Bc6449
> block number: 5
> block timestamp: 1637999206
> account: 0x19258f77721790dB2cEBe89fdBE1f241BCA95202
> balance: 99.97578014
> gas used: 425863 (0x67f87)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00851726 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00851726 ETH
Summary
=======
> Total deployments: 3
> Final cost: 0.02281934 ETH
· 실행
실행은 해당 컨트랙트의 함수 및 변수를 콘솔모드에서 호출할 수 있습니다.
$ truffle console --network dev
truffle(dev)>
truffle(network)>는 콘솔모드입니다.
▶ 배포한 컨트랙트 가져오기
truffle(dev)> contract = await Mung.deployed()
▶ text 변수 값 가져오기
truffle(dev)> contract.text.call()
'Hello Mung~'
▶ setText() 함수 호출
truffle(dev)> contract.setText( 'mung', { from: accounts[0] } )
{
tx: '0x06e58ff5e6c6f3dd45d433549a0c0a299d88dea3d621c8f6b1ec5bc44984f8d2',
receipt: {
transactionHash: '0x06e58ff5e6c6f3dd45d433549a0c0a299d88dea3d621c8f6b1ec5bc44984f8d2',
transactionIndex: 0,
blockHash: '0x8bd35ce4b109100374e15f3bfe70995fa0e2f9a2ff534ec69fbc1b05e6c1ed15',
blockNumber: 7,
from: '0x19258f77721790db2cebe89fdbe1f241bca95202',
to: '0x680d78685de52f50201bc4e5b1723daf44bc6449',
gasUsed: 29574,
cumulativeGasUsed: 29574,
contractAddress: null,
logs: [],
status: true,
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
rawLogs: []
},
logs: []
}
setText()는 트랜잭션을 발생하기 위해 마지막 인자로 트랜잭션을 발생할 주소를 전달합니다. 만약 {from: accounts[0]}이 없다면 기본으로 accounts[0]가 트랜잭션을 만듭니다.
▶ text 변수 값 가져오기
truffle(dev)> contract.text.call()
'mung'
truffle(dev)> contract.text()
'mung'
원하는 스타일로 작성합니다. 개인적으로 call()을 호출해서 사용하는 방법을 선호합니다.
▶ error를 일으키는 함수 정상 호출
truffle(dev)> contract.errorOccur(1, { from: accounts[0]})
BN { negative: 0, words: [ 1, <1 empty item> ], length: 1, red: null }
▶ error를 일으키는 함수 정상 호출
truffle(dev)> contract.errorOccur(0, { from: accounts[0]})
Uncaught:
Error: Returned error: VM Exception while processing transaction: revert hello world error
· 테스트하기
코드가 바뀔 때마다 앞의 과정을 반복적으로 한다면 아주 멍개같은 짓 입니다. 똑개는 절대 저렇게 테스트하지 않습니다.
테스트 코드는 test 아래에서관리합니다.
그 전에 라이브러리 하나를 설치하겠습니다.
$ npm install --save truffle-assertions
truffle-assertions은 revert가 발생하는 것을 테스트할 수 있습니다.
파일명: test/test_mung.js
const truffleAssert = require('truffle-assertions');
const Mung = artifacts.require("Mung");
contract("Mung", accounts => {
// 각각의 테스트 케이스인 it을 실행할 때마다 before 실행하여 스마트 컨트랙트 객체를 가져옴
before(async () => {
this.instance = await Mung.deployed();
})
it("should be initialized with correct value", async () => {
// 스마트 컨트랙트에 정의된 text() 함수 호출
const text = await this.instance.text();
assert.equal(text, "Hello Mung~", "Wrong initialized value!");
});
it("should change the text", async () => {
const changedText = 'hoho';
// 스마트 컨트랙트에 정의된 setText() 함수 호출
// 마지막 객체는 트랜잭션 발생 정보를 가진다.
await this.instance.setText(changedText, {from: accounts[0]});
// 스마트 컨트랙트에 정의된 say() 함수 호출
const text = await this.instance.text();
assert.equal(text, changedText, "dose not change the value!");
});
it("should throw exception", async () => {
// 스마트 컨트랙트에 정의된 errorOccur() 함수 호출
await truffleAssert.reverts(
this.instance.errorOccur(0, {from: accounts[0]}),
"hello world error"
)
});
it("should not throw exception", async () => {
// 스마트 컨트랙트에 정의된 errorOccur() 함수 호출
const rst = await this.instance.errorOccur(1, {from: accounts[0]});
assert.equal(rst.words[0], 1, "ErrorOccur event not emitted!");
});
})
$ truffle test --network dev
Using network 'dev'.
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Contract: HelloWorld
✓ should be initialized with correct value
✓ should change the greeting (199ms)
✓ should throw exception (239ms)
Contract: Mung
✓ should be initialized with correct value
✓ should change the text (190ms)
✓ should throw exception
✓ should not throw exception
7 passing (838ms)
여기까지 진행했다면 다음과 같은 구조가 됩니다.
.
├── README.md
├── build
│ └── contracts
│ ├── Migrations.json
│ └── Mung.json
├── contracts
│ ├── Migrations.sol
│ └── Mung.sol
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_Mung.js
├── package-lock.json
├── package.json
├── test
│ └── test_mung.js
└── truffle-config.js
5 directories, 11 files