본문 바로가기
Hack/Cryptocurrency

Ethereum 로컬 개발 환경 구성하기 (with Smart Contract)

by Becoming a Hacker 2023. 7. 23.
반응형

사실 Etehreum은 Test Nets이 있기 때문에 Remix와 같은 도구들을 통하여 Test Nets에 연결하여 쉽게 이용할 수 있습니다.

 

그러나 Test Nets은 아래와 같은 일부 단점들을 가지고 있어, 로컬에서 개발 환경이 필요한 경우가 점점 늘어나고 있습니다.

  • 외부적으로 공개가 되어있어 Private하지 못함
  • Test Ether를 구하는 것이 점점 까다로워지고 있음
  • 사용자가 많아짐에 따라 Transaction 처리 속도가 느려지고 있음

 

Ethereum 재단에서도 로컬에서 개발 환경을 구축할 수 있는 여러 Framework들을 안내해주고 있습니다.

 

Home | ethereum.org

Ethereum is a global, decentralized platform for money and new kinds of applications. On Ethereum, you can write code that controls money, and build applications accessible anywhere in the world.

ethereum.org

 

그 중 가장 많은 Star를 보유한 Truffle을 이용하여 로컬 개발 환경을 구축해보고자 합니다.

선정 이유 : 가장 많은 Star를 보유하고 있어 커뮤니티가 활성화되어 있고 최근까지 꾸준히 Release가 되고 있음


Truffle

Truffle은 Ethereum을 기반으로 한 Dapp(Decentralized App)을 쉽게 개발할 수 있도록 도와주는 BlockChain Framewok입니다.

 

Truffle을 이용하면, Smart Contract Compile과 Depoly 그리고 Test까지 손쉽게 할 수 있다는 장점을 가지고 있습니다.

 

설치 방법

1. npm을 통한 truffle, Ganache Framework 설치

Ganache는 Ethereum Simulator

$ npm install -g truffle
$ npm install -g ganache-cli

 

사용 방법

1. Truffle 환경 구축

 

truffle init 명령어를 통하여 환경을 구축합니다.

$ mkdir {project_name}
$ cd {project_name}
$ truffle init

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

> Copying project files to C:\Users\USER\evm

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

 

2. Contract 생성

 

Contract 생성 명령어 실행 후 contracts 폴더에 생성된 {Contract_Name}.sol 파일을 수정합니다.

$ truffle create contract {Contract_Name}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Test {
  bytes32 data;
  event Data(bytes32 old_data, bytes32 new_data);

  constructor(bytes32 _data){
    emit Data(0x0, _data);
    data = _data;
  }

  function setData(bytes32 _data) public {
    emit Data(data, _data);
    data = _data;
  }

  function getData() view public returns (bytes32){
    return data;
  }

}

 

3. Deploy Script 작성

 

Depoly Script 생성 후, migrations 폴더에 생성된 {date}_deploy.js 파일을 수정합니다.

$ truffle create migration deploy
var Test = artifacts.require("Test");

module.exports = function(_deployer) {
  _deployer.deploy(Test)
};

 

4. Compile

 

truffle compile 명령어를 통하여 Contract Code를 Compile 합니다.

$ truffle compile

Compiling your contracts...
===========================
√ Fetching solc version list from solc-bin. Attempt #1
√ Downloading compiler. Attempt #1.
> Compiling .\contracts\Test.sol
> Compilation warnings encountered:

    Warning: Visibility for constructor is ignored. If you want the contract to be non-deployable, making it "abstract" is sufficient.
 --> project:/contracts/Test.sol:7:3:
  |
7 |   constructor(bytes32 _data) public {
  |   ^ (Relevant source part starts here and spans across multiple lines).


> Artifacts written to C:\Users\USER\evm\build\contracts
> Compiled successfully using:
   - solc: 0.8.20+commit.a1b79de6.Emscripten.clang

 

5. Ganache를 통한 EVM 환경 구성

 

ganache-cli를 통하여 EVM 환경을 구성할 예정입니다.

(GUI가 훨씬 Transaction을 확인하기 좋지만, 공부용이기 때문에 더 많은 삽질을 할 예정)

-d : 미리 정의된 Mnemonic 기반으로 Deterministic Address 생성

-m {word} : 정의된 단어를 Mnemonic 단어로 사용하여 계정 생성

> ganache-cli -d -m Test
Ganache CLI v6.12.2 (ganache-core: 2.13.2)

Available Accounts
==================
(0) 0xD253792Ef4de982dC99dF3C43CF3c02b62f366Ef (100 ETH)
(1) 0xd3F7E8ad5513De44c792cfFA78771e4005003d0d (100 ETH)
(2) 0xc13C773c58eE2D03429700FC2216a95735BF51fB (100 ETH)
(3) 0xadC808e97e87C84faABC3c828b091366DF4bCDF7 (100 ETH)
(4) 0xa34485669C2641f6C55039EA4fE2e7B4624e7B8A (100 ETH)
(5) 0xe4bE9B5E1DC2c8F0Bfa39fE86539bC5A1ABf4ABd (100 ETH)
(6) 0xAD2aBCD5a9756e63DC8349b97e0e0B324310AeDC (100 ETH)
(7) 0x37b46464e8e9f24dFC5E961d27D23F8afAC88264 (100 ETH)
(8) 0x2C5805677DB9cF0d217005D28A96F8845D7fC4E8 (100 ETH)
(9) 0xd8Cd0b7FC89afd2d2371A7b8918AeD6aBeD78dD4 (100 ETH)

Private Keys
==================
(0) 0x8f2fce7094b41cd06732491726c9602fe7b02b5d86b74e43a84b7955ca12dde1
(1) 0x5e1f808419db505da85378b42d52cfc9ce67ad3043404aceec7185a167601bdb
(2) 0x0fdbb2b44e9d0d082edb008654fc68d80a09a2283d1556e4f771063d117b9253
(3) 0x65e058d2983e03e1669067f4799f4f4f1d7f7e7ab050681fe41f9c808a45ce45
(4) 0x6d0dcf0d50e11e349f066e20c99f448c3789ce74acf1664fe403c107c804b3bd
(5) 0x734057d7f311504cd12b9c297b83e69e14a332b086a078b981e35828a95f1bc6
(6) 0xf3925555a35294519b4061c577fbf198eca0cca58d1aee317a8efbe1140cb96d
(7) 0x6e3eee7374a39acd4c1ef13b1c22966c0640fa066e2538959340db4d7b45337a
(8) 0xcf24621640379d0ef7c46ad4aeff219c84dffe7a34a377e62b2e361bd6d9ed59
(9) 0x3b70dbd8fe33e0a98457227241627d7e0b504cfc3ab3667e267235acc49a450e

HD Wallet
==================
Mnemonic:      Test
Base HD Path:  m/44'/60'/0'/0/{account_index}

Gas Price
==================
20000000000

Gas Limit
==================
6721975

Call Gas Limit
==================
9007199254740991

Listening on 127.0.0.1:8545

 

그리고 truffle-config.js 파일에서 Network를 Ganache의 Default Address로 설정합니다.

※ 이때 Soliditiy 버전에 맞춰 Compiler를 설정해줘야 Deploy 시 에러가 발생하지 않습니다.

module.exports = {
  /**
   * Networks define how you connect to your ethereum client and let you set the
   * defaults web3 uses to send transactions. If you don't specify one truffle
   * will spin up a managed Ganache instance for you on port 9545 when you
   * run `develop` or `test`. You can ask a truffle command to use a specific
   * network from the command line, e.g
   *
   * $ truffle test --network <network-name>
   */

  networks: {
    // Useful for testing. The `development` name is special - truffle uses it by default
    // if it's defined here and no other network is specified at the command line.
    // You should run a client (like ganache, geth, or parity) in a separate terminal
    // tab if you use this network and you must also set the `host`, `port` and `network_id`
    // options below to some value.
    //
    development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 8545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
    },
    
  compilers: {
    solc: {
      version: "0.8.0",      // Fetch exact version from solc-bin (default: truffle's version)
    }
  },

 

6. truffle을 이용한 Deploy

 

Truffle을 통한 Deploy 시 중복 배포가 되지 않도록 방지해주는 기능이 있는데요. 재배포시에는 --reset 옵션을 통해 재배포한다는 점을 명시해줘야 합니다.

> truffle migrate --reset

Compiling your contracts...
===========================
√ Fetching solc version list from solc-bin. Attempt #1
√ Downloading compiler. Attempt #1.
> Compiling .\contracts\Test.sol
> Artifacts written to C:\Users\USER\evm\build\contracts
> Compiled successfully using:
   - solc: 0.8.0+commit.c7dfd78e.Emscripten.clang


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


1690069831_deploy.js
====================

   Deploying 'Test'
   ----------------
   > transaction hash:    0xc0ad7ff121530982f1a343fc6c3bb178cf4887a4f8f9c327f72dbe460b2f5590
   > Blocks: 0            Seconds: 0
   > contract address:    0x7A5c6D82a52E3522AaE63B79238Aa442fe31426a
   > block number:        1
   > block timestamp:     1690074148
   > account:             0xD253792Ef4de982dC99dF3C43CF3c02b62f366Ef
   > balance:             99.99717192
   > gas used:            141404 (0x2285c)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00282808 ETH

   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00282808 ETH

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

 

Ganache Log

eth_getBlockByNumber
eth_gasPrice
eth_sendTransaction

  Transaction: 0xc0ad7ff121530982f1a343fc6c3bb178cf4887a4f8f9c327f72dbe460b2f5590
  Contract created: 0x7a5c6d82a52e3522aae63b79238aa442fe31426a
  Gas usage: 141404
  Block Number: 1
  Block Time: Sun Jul 23 2023 10:02:28 GMT+0900 (대한민국 표준시)

eth_getTransactionReceipt
eth_getCode
eth_getTransactionByHash
eth_getBlockByNumber
eth_getBalance

 

7. Truffle Console를 통한 Contract 접근

 

먼저 truffle console에 접근합니다.

(이후 명령어는 console 상에서 입력한 명령어 입니다.)

$ truffle console

 

Test Contract를 배포하였다고 가정하였을 때, 아래의 명령어를 통해 배포된 Contract를 확인할 수 있습니다.

> Test
...
 _json: {
    contractName: 'Test',
    abi: [ [Object], [Object], [Object] ],
    metadata: '{"compiler":{"version":"0.8.0+commit.c7dfd78e"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"bytes32","name":"_data","type":"bytes32"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"getData","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_data","type":"bytes32"}],"name":"setData","outputs":[],"stateMutability":"nonpayable","type":"function"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"compilationTarget":{"project:/contracts/Test.sol":"Test"},"evmVersion":"istanbul","libraries":{},"metadata":{"bytecodeHash":"ipfs"},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"project:/contracts/Test.sol":{"keccak256":"0x203da0963f68c957910727bca7e23fa42268ece44dd1d910395df1fc9ef5f0a3","license":"MIT","urls":["bzz-raw://becf06b7121dbcdff0c2fd7114d96cbe1c91d86f2f0dce666778878ec668aea2","dweb:/ipfs/QmcHi78nabJSXd1iEY9RfMnAWi8ZQHThSsgts8iyvCyFSa"]}},"version":1}',
    bytecode: '0x608060405234801561001057600080fd5b506040516101dc3803806101dc83398181016040528101906100329190610054565b806000819055505061009e565b60008151905061004e81610087565b92915050565b60006020828403121561006657600080fd5b60006100748482850161003f565b91505092915050565b6000819050919050565b6100908161007d565b811461009b57600080fd5b50565b61012f806100ad6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80633bc5de3014603757806341657ee6146051575b600080fd5b603d6069565b6040516048919060c2565b60405180910390f35b6067600480360381019060639190608f565b6072565b005b60008054905090565b8060008190555050565b60008135905060898160e5565b92915050565b60006020828403121560a057600080fd5b600060ac84828501607c565b91505092915050565b60bc8160db565b82525050565b600060208201905060d5600083018460b5565b92915050565b6000819050919050565b60ec8160db565b811460f657600080fd5b5056fea2646970667358221220a45596a75d68299424c673f5c9761fae625321ef7970ae577d54c99e8722b8ca64736f6c63430008000033',
    deployedBytecode: '0x6080604052348015600f57600080fd5b506004361060325760003560e01c80633bc5de3014603757806341657ee6146051575b600080fd5b603d6069565b6040516048919060c2565b60405180910390f35b6067600480360381019060639190608f565b6072565b005b60008054905090565b8060008190555050565b60008135905060898160e5565b92915050565b60006020828403121560a057600080fd5b600060ac84828501607c565b91505092915050565b60bc8160db565b82525050565b600060208201905060d5600083018460b5565b92915050565b6000819050919050565b60ec8160db565b811460f657600080fd5b5056fea2646970667358221220a45596a75d68299424c673f5c9761fae625321ef7970ae577d54c99e8722b8ca64736f6c63430008000033',

> Test.address
'0x7A5c6D82a52E3522AaE63B79238Aa442fe31426a'

 

truffle에서 제공하는 deployed() 메소드를 통하여 Promise 객체를 tinstance 변수에 할당하였습니다.

> Test.deployed().then(instance => tinstance = instance)

 

이후, tinstance 변수를 이용하여 Contract의 함수들을 실행시킬 수 있습니다. Test Contract의 getData()와 setData()를 실행해본 결과, 정상적으로 작동하는 것을 확인하였습니다.

> tinstance.getData()
'0x5445535420444154413200000000000000000000000000000000000000000000'
> web3.utils.hexToAscii('0x5445535420444154413200000000000000000000000000000000000000000000')
'TEST DATA2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

> tinstance.setData(web3.utils.asciiToHex("TEST DATA2"))
{
  tx: '0x035a02dafe5767e2a486683671e874f20a2a2543decf587b6b19a0b9b73e4f99',
  receipt: {
    transactionHash: '0x035a02dafe5767e2a486683671e874f20a2a2543decf587b6b19a0b9b73e4f99',
    transactionIndex: 0,
    blockHash: '0xa6cadfb333caf1ebb18cfcf755da042fd8e7a506243e3bc5ad2721162da06cac',
    blockNumber: 2,
    from: '0xd253792ef4de982dc99df3c43cf3c02b62f366ef',
    to: '0x7a5c6d82a52e3522aae63b79238aa442fe31426a',
    gasUsed: 26732,
    cumulativeGasUsed: 26732,
    contractAddress: null,
    logs: [],
    status: true,
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
    rawLogs: []
  },
  logs: []
}

> tinstance.getData()
'0x5445535420444154413200000000000000000000000000000000000000000000'
> web3.utils.hexToAscii('0x5445535420444154413200000000000000000000000000000000000000000000')
'TEST DATA2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00

 

 

8. Test Script 작성 및 Unit Test

 

Test Script 생성 명령어 실행 후 test 폴더 내에 있는 {Script_Name} 파일을 수정합니다.

$ truffle create test {Script_Name}
const Test = artifacts.require("Test");

contract("Test", (accounts) => {
  it("should set and get data correctly", async () => {

    const testContract = await Test.new(web3.utils.asciiToHex("TEST DATA"));

    const getData = await testContract.getData();
    assert.equal(web3.utils.hexToUtf8(getData), "TEST DATA", "data is not TEST DATA");
    console.log("data is "+web3.utils.hexToUtf8(getData))

    await testContract.setData(web3.utils.asciiToHex("TEST DATA2"));

    const new_data = await testContract.getData();
    assert.equal(web3.utils.hexToUtf8(new_data), "TEST DATA2", "data is not TEST DATA2");
    console.log("data is "+web3.utils.hexToUtf8(new_data))
  });
});

 

이후 truffle test 명령어를 통하여 Unit Test를 진행할 수 있습니다!

$ truffle test
Using network 'development'.


Compiling your contracts...
===========================
> Compiling .\contracts\Test.sol
> Artifacts written to C:\Users\USER\AppData\Local\Temp\test--7980-6morP93BUTFD
> Compiled successfully using:
   - solc: 0.8.0+commit.c7dfd78e.Emscripten.clang


  Contract: Test
data is TEST DATA
data is TEST DATA2
    √ should set and get data correctly (726ms)

 

참고로 assert 구문을 통해 Test에 대한 검증을 수행할 수 있는데요. 검증이 잘못되었다면, 아래와 같은 에러 로그가 나타납니다.

$ truffle test
Using network 'development'.


Compiling your contracts...
===========================
> Compiling .\contracts\Test.sol
> Artifacts written to C:\Users\USER\AppData\Local\Temp\test--25876-uRDntSBYake3
> Compiled successfully using:
   - solc: 0.8.0+commit.c7dfd78e.Emscripten.clang


  Contract: Test
    1) should set and get data correctly

    Events emitted during test:
    ---------------------------

    [object Object].Data(
      old_data: 0x0000000000000000000000000000000000000000000000000000000000000000 (type: bytes32),
      new_data: 0x5445535420444154410000000000000000000000000000000000000000000000 (type: bytes32)
    )


    ---------------------------


  0 passing (517ms)
  1 failing

  1) Contract: Test
       should set and get data correctly:

      data is not TEST DATA
      + expected - actual

      -TEST DATA
      +1

      at Context.<anonymous> (test\test.js:9:12)
      at processTicksAndRejections (node:internal/process/task_queues:95:5)

 

댓글