[Ethereum] 로컬 프라이빗 네트워크에 스마트 컨트랙트 배포

로컬 프라이빗 네트워크에 스마트 컨트랙트를 배포하고 호출해보겠습니다.

트러플 프로젝트 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
% mkdir ~/truffleproject
% cd ~/truffleproject
truffleproject % truffle init

Starting init...
================

> Copying project files to /Users/devsawd/truffleproject

Init successful, sweet!

Try our scaffold commands to get started:
$ truffle create contract YourContractName # scaffold a contract
$ truffle create test YourTestName # scaffold a test

http://trufflesuite.com/docs

이더리움 네트워크 설정

~/truffleproject/truffle-config.js에서 Geth 프라이빗 네트워크, 가나슈, 솔리디티 컴파일 버전 관련 설정을 진행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
networks: {
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
network_id: "15", // Any network (default: none)
gas : 4700000
},
ganache: {
host: '127.0.0.1',
port: 7545,
network_id: '*'
},
},
compilers: {
solc: {
version: "0.8.17" // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
// settings: { // See the solidity docs for advice about optimization and evmVersion
// optimizer: {
// enabled: false,
// runs: 200
// },
// evmVersion: "byzantium"
// }
}
}
}

Truffle Console 실행

1
2
% truffle console --network development
truffle(development)>

만약 아래와 같은 에러 발생시 Geth가 실행되어있는지 확인합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
% truffle console --network development
> Something went wrong while attempting to connect to the network at http://127.0.0.1:8545. Check your network configuration.
ProviderError:
Could not connect to your Ethereum client with the following parameters:
- host > 127.0.0.1
- port > 8545
- network_id > 15
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)

at /opt/homebrew/lib/node_modules/truffle/build/webpack:/packages/provider/wrapper.js:80:1
at /opt/homebrew/lib/node_modules/truffle/build/webpack:/packages/provider/wrapper.js:118:1
at XMLHttpRequest.request.onreadystatechange (/opt/homebrew/lib/node_modules/truffle/build/webpack:/node_modules/web3-providers-http/lib/index.js:98:1)
at XMLHttpRequestEventTarget.dispatchEvent (/opt/homebrew/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request-event-target.js:34:1)
at XMLHttpRequest.exports.modules.996763.XMLHttpRequest._setReadyState (/opt/homebrew/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:208:1)
at XMLHttpRequest.exports.modules.996763.XMLHttpRequest._onHttpRequestError (/opt/homebrew/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:349:1)
at ClientRequest.<anonymous> (/opt/homebrew/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:252:48)
at ClientRequest.emit (node:events:513:28)
at ClientRequest.emit (node:domain:482:12)
at Socket.socketErrorListener (node:_http_client:481:9)
at Socket.emit (node:events:513:28)
at Socket.emit (node:domain:482:12)
at emitErrorNT (node:internal/streams/destroy:151:8)
at emitErrorCloseNT (node:internal/streams/destroy:116:3)
at processTicksAndRejections (node:internal/process/task_queues:82:21)
Truffle v5.6.9 (core: 5.6.9)
Node v18.6.0

package.json 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
% npm init -y    
Wrote to /Users/devsawd/truffleproject/package.json:

{
"name": "truffleproject",
"version": "1.0.0",
"description": "",
"main": "truffle-config.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

오픈제플린 설치

안전한 스마트 컨트랙트 개발을 위한 라이브러리입니다.

ERC20 및 ERC721과 같은 표준 구현을 지원합니다.

이 실습에서 꼭 필요한 라이브러리는 아니지만 학습에 참고하면 유용합니다.

1
2
3
4
5
% npm install @openzeppelin/contracts

added 1 package, and audited 2 packages in 3s

found 0 vulnerabilities

스마트 컨트랙트 구현

스마트 컨트랙트로 이더를 전송하는 간단한 함수를 구현해보겠습니다.

오픈제플린의 Ownable을 어떻게 import해서 사용하는지 참고하면 좋습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% cd contracts
% touch Sample.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";

contract Sample is Ownable{
function send() public payable {
address sender = payable(msg.sender);
bool sent = payable(sender).send(msg.value);
require(sent,"Failed to send either");
}
}

컴파일

1
2
3
4
5
6
7
8
9
10
% truffle compile

Compiling your contracts...
===========================
> Compiling ./contracts/Sample.sol
> Compiling @openzeppelin/contracts/access/Ownable.sol
> Compiling @openzeppelin/contracts/utils/Context.sol
> Artifacts written to /Users/devsawd/truffleproject/build/contracts
> Compiled successfully using:
- solc: 0.8.17+commit.8df45f5f.Emscripten.clang

프라이빗 네트워크에 계약 배포

마이그레이션 파일 작성

1
2
3
4
5
6
7
8
9
10
% cd ~/truffleproject/migrasions
% touch 2_deploy_sample.js

const Sample = artifacts.require('../contracts/Sample.sol')

module.exports = deployer => {
deployer.deploy(Sample).then(instance => {
console.log('ABI:', JSON.stringify(instance.abi))
})
}

가나슈에 트러플 프로젝트 배포

앞서 설정한 가나슈 네트워크에 배포합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
% truffle migrate --network ganache

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.


Starting migrations...
======================
> Network name: 'ganache'
> Network id: 5777
> Block gas limit: 6721975 (0x6691b7)


2_deploy_sample.js
==================

Deploying 'Sample'
------------------
> transaction hash: 0xcca8f14829c106e3bf74d00282e9323358bc8904eee76a2a0469bbbe159b4eae
> Blocks: 0 Seconds: 0
> contract address: 0x8e36a247aD15a98ae7e7B6e23D4D69988bE9c12e
> block number: 1
> block timestamp: 1671726245
> account: 0x9313a92d0F000dBf856252A1cd3E176E116fc704
> balance: 99.9921389
> gas used: 393055 (0x5ff5f)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.0078611 ETH

ABI: [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event","signature":"0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","constant":true,"signature":"0x8da5cb5b"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function","signature":"0x715018a6"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function","signature":"0xf2fde38b"},{"inputs":[],"name":"send","outputs":[],"stateMutability":"payable","type":"function","payable":true,"signature":"0xb46300ec"}]
> Saving artifacts
-------------------------------------
> Total cost: 0.0078611 ETH

Summary
=======
> Total deployments: 1
> Final cost: 0.0078611 ETH

만약 컴파일과 마이그레이션을 처음부터 실행할때는 아래의 명령어를 이용합니다.

1
truffle migrate --reset --compile-all --network ganache

스마트 컨트랙트 호출 확인

Geth 콘솔에 접속 후 계정 잠금 해제

계정이 잠겨있으면 send 함수를 호출할 수 없으니 계정의 잠금을 해제해줍니다.

1
2
3
4
5
$ geth attach http://localhost:8545
> web3.personal.unlockAccount(eth.accounts[1])
Unlock account 0x66b4e7be902300f9a15d900822bbd8803be87391
Passphrase:
true

계정 잔액 확인

이더를 보내기 전의 잔액을 확인합니다.

1
2
3
> web3.fromWei(eth.getBalance(eth.accounts[1]))
499999999.9999319599999997
>

스마트 컨트랙트 객체

트러플 프로젝트를 배포한 후 스마트 컨트랙트의 기능을 호출하기 위해 스마트 컨트랙트 객체를 가져옵니다.

1
2
# 예시
> eth.contract({ABI}).at({contract address})

가나슈에 트러플 프로젝트 배포의 로그를 확인해 {ABI}{contract address}에 알맞은 값을 입력해 Sample 객체를 가져옵니다.

1
2
> var sample = eth.contract([{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event","signature":"0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","constant":true,"signature":"0x8da5cb5b"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function","signature":"0x715018a6"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function","signature":"0xf2fde38b"},{"inputs":[],"name":"send","outputs":[],"stateMutability":"payable","type":"function","payable":true,"signature":"0xb46300ec"}]).at('0x8e36a247aD15a98ae7e7B6e23D4D69988bE9c12e')
undefined

스마트 컨트랙트를 확인합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
> sample
{
abi: [{
anonymous: false,
inputs: [{...}, {...}],
name: "OwnershipTransferred",
signature: "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0",
type: "event"
}, {
constant: true,
inputs: [],
name: "owner",
outputs: [{...}],
signature: "0x8da5cb5b",
stateMutability: "view",
type: "function"
}, {
inputs: [],
name: "renounceOwnership",
outputs: [],
signature: "0x715018a6",
stateMutability: "nonpayable",
type: "function"
}, {
inputs: [{...}],
name: "transferOwnership",
outputs: [],
signature: "0xf2fde38b",
stateMutability: "nonpayable",
type: "function"
}, {
inputs: [],
name: "send",
outputs: [],
payable: true,
signature: "0xb46300ec",
stateMutability: "payable",
type: "function"
}],
address: "0x8e36a247aD15a98ae7e7B6e23D4D69988bE9c12e",
transactionHash: null,
OwnershipTransferred: function bound(),
allEvents: function bound(),
owner: function bound(),
renounceOwnership: function bound(),
send: function bound(),
transferOwnership: function bound()
}
>

스마트 컨트랙트의 잔고 확인

계정으로부터 이더를 전송하기 전 스마트 컨트랙트의 잔고를 확인합니다.

1
2
> web3.fromWei(eth.getBalance('0x8e36a247aD15a98ae7e7B6e23D4D69988bE9c12e'))
0

함수 호출

아래의 방법으로 함수를 트랜잭션 방식으로 호출합니다.

1
계약객체.함수이름.sendTransaction(인자, {from: 호출주소, gas: 가스제한, value: 송금액})

value는 필요없으면 생략이 가능합니다.

send 함수를 transaction으로 호출합니다.
이때 accounts[1]에서 스마트 컨트랙트로 0.1 이더를 전송해보겠습니다.

1
2
> sample.send.sendTransaction({from: eth.accounts[1], gas: 100000, value: web3.toWei(0.1, 'ether')})
"0x86e3b84c28caad0874e9933c42e02e3deebd42c7edf8b1d0c07f0b47c12de6cd"

트랜잭션 발행 결과 확인

0.1 이더 전송시 발생한 트랜잭션으로 결과를 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> eth.getTransaction('0x86e3b84c28caad0874e9933c42e02e3deebd42c7edf8b1d0c07f0b47c12de6cd')
{
blockHash: "0x4d45695ee99e9271727659d29f18c32aca450db64bb8302e8478aa469d79f6ad",
blockNumber: 5575,
chainId: "0xf",
from: "0x66b4e7be902300f9a15d900822bbd8803be87391",
gas: 100000,
gasPrice: 1000000000,
hash: "0x86e3b84c28caad0874e9933c42e02e3deebd42c7edf8b1d0c07f0b47c12de6cd",
input: "0xb46300ec",
nonce: 3,
r: "0x6e690cb0b3eff501580b6dd1e66a98d73140957f1d639e25baf8b57ea222971f",
s: "0x67f8d94c622c528d5dfdc44fdfcfa579896eb2dd1c9fd4a653aa4d4b8b4dc167",
to: "0x8e36a247ad15a98ae7e7b6e23d4d69988be9c12e",
transactionIndex: 0,
type: "0x0",
v: "0x42",
value: 100000000000000000
}

만약 blockNumbernull이라면 채굴해서 트랜잭션을 블록에 포함시킵니다.

1
2
3
4
5
6
7
> miner.start(1)
null

# 약간의 시간이 지난 후 트랜잭션 발행 결과 확인

> miner.stop()
null

스마트 컨트랙트 잔액 확인

1
2
> web3.fromWei(eth.getBalance('0x8e36a247aD15a98ae7e7B6e23D4D69988bE9c12e'))
0.1

계정 잔액 확인

1
2
> web3.fromWei(eth.getBalance(eth.accounts[1]))
499999999.8999107519999997