Post

스마트 컨트랙트 실행하기

스마트 컨트랙트 실행하기

Solidity Compiler 설치하기

npm을 이용하여 설치해보자.

1
% sudo npm install -g solc

프로그래밍 예제

VSC에서 hello.sol 파일을 만들고 프로그래밍 해보자.

1
2
3
4
5
6
7
8
9
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.4.24;

contract hello{
    string message = "Hello world!";
    function showMsg() public view returns (string memory){
        return message;
    }
}

컴파일

npm을 이용하여 설치해주었기 때문에, 기본 명령어가 solc 가 아닌 solcjs이다!

1
% solcjs --abi --bin hello.sol

성공적으로 컴파일이 되면 해당 폴더 내 .abi, .bin파일이 생성된다.

  • abi (Application Binary Interface) : 스마트 컨트랙트 코드의 설명이 담긴 JSON 파일
    • 어떤 함수가 들어있는지 설명하는 것.
  • bin : 컴파일 된 바이너리 파일

실행하기

콘솔 접속

먼저, 미리 만들어둔 사설 네트워크의 콘솔에 접속한다.

저는 포트가 다른 프로세스와 겹쳐 임시로 30000으로 설정하였습니다.

1
geth --datadir cslab --port 30000 --nodiscover console

콘솔에서 bin, abi 변수를 각각 설정한다.

  • bin : 컴파일된 bin파일을 설정해줌.
  • abi : json파일 객체 그대로 설정.

변수 설정

bin 변수 설정

  • bin파일을 그대로 복사하여 앞에 16진수를 의미하는 0x를 붙여 변수로 설정한다.
  • 또한 문자열을 의미하는 “” 따옴표로 감싸서 변수 설정을 해준다.
    1
    
    > bin = "0x{컴파일된 16진수 bin파일 내용}"
    

abi 변수 설정

  • bin과 다르게 string 문자열이 아닌 객체 형태로 바로 설정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
> abi = [{"inputs":[],"name":"showMsg","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]

// 결과
[{
    inputs: [],
    name: "showMsg",
    outputs: [{
        internalType: "string",
        name: "",
        type: "string"
    }],
    stateMutability: "view",
    type: "function"
}]

이더리움에서 실행하기

sendTransaction() 함수는 주소를 반환한다.

  • 여기서는 tx라는 변수에 주소를 저장한다.
1
> tx = eth.sendTransaction({from: eth.accounts[0], data: bin})


하지만 위 명령어를 실행하면 다음과 같이 Authentication 에러가 발생한다.

img

그러므로, 0번 계좌의 비밀번호를 풀어주는 과정이 필요하다.

1
> personal.unlockAccount(eth.accounts[0])

그리고 다시 sendTransaction() 을 실행시켜주면! 성공적으로 실행된다.

채굴

하지만 현재는 바이트코드가 블록에는 들어갔으나, 다른 블록체인과 연결되지는 못한 상태이다.

1
2
> eth.getTransactionReceipt(tx)
null

따라서 거래는 되었지만 블록체인에 연결이 되지 않아 결과값이 null인 것을 확인할 수 있다.

1
2
> miner.start()
> miner.stop()

위 명령어를 통해 채굴을 시작하면 블록체인에 연결된다. 몇 초 기다린 뒤, stop() 해주도록 하자.

그리고 다시 receipt(부가 트랜잭션 정보)를 확인하면? 다음과 같은 결과값을 얻을 수 있다.

1
> eth.getTransactionReceipt(tx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  blockHash: "0xa49a284293d151db5b61f8cf2b50b4fc61d58e01a46b1a6ea6f10ecebf040e9f",
  blockNumber: 7,
  contractAddress: "0x71776219f07d03224d3c615a6fc22ad450bb3753",
  cumulativeGasUsed: 3925000,
  from: "0xc71a5b838cf49fd8bd7db6e1493ae0222f8acf3c",
  gasUsed: 300000,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  status: "0x0",
  to: null,
  transactionHash: "0xaaa6c78db34de837020fb61cbfb02c4e64c75b0f745b07b52a9caac32cba4f6f",
  transactionIndex: 3
}

특히 이 결과값에서 contractAddress를 주로 사용하게 될 건데,
contractAddress란? 바이너리 코드가 들어간 위치(스마트 컨트랙트가 들어간 블록의 주소)이다.

아래 명령어를 통해 contractAddress를 address라는 변수에 저장해주도록 하자.

1
2
> address = eth.getTransactionReceipt(tx).contractAddress
"0x71776219f07d03224d3c615a6fc22ad450bb3753"

계약 인스턴스 가져오기

스마트 컨트랙트를 다음과 같이 배포하고, eth.contract()를 사용하여 계약 인스턴스를 가져오고 메서드들을 호출할 수 있다.

1
> contractInterface = eth.contract(abi).at(address)
1
2
3
4
5
6
7
8
9
10
11
12
13
{
  abi: [{
      inputs: [],
      name: "showMsg",
      outputs: [{...}],
      stateMutability: "view",
      type: "function"
  }],
  address: "0x71776219f07d03224d3c615a6fc22ad450bb3753",
  transactionHash: null,
  allEvents: function(),
  showMsg: function()
}

이 내용은 위에서 작성한 hello.sol 코드의 내용이다. 여기서 우리가 작성한 showMsg() 함수를 실행해보자.

함수 실행

1
2
> contractInterface.showMsg.call()
"Hello world!"

트러블 슈팅

Error: intrinsic gas too low

아래와 같은 에러가 발생한다면 배포하거나 호출하는 트랜잭션에서 가스 한도를 너무 적게 설정했을 가능성이 크다!

1
2
3
4
5
6
> tx = eth.sendTransaction({from: eth.accounts[0], data: bin})
Error: intrinsic gas too low
    at web3.js:3143:20
    at web3.js:6347:15
    at web3.js:5081:36
    at <anonymous>:1:6

다음과 같이 gas:3000000 충분한 가스를 할당할 경우 해결할 수 있다.

1
2
3
> tx = eth.sendTransaction({from: eth.accounts[0], data: bin, gas:3000000})
INFO [12-16|18:23:07.052] Submitted contract creation              fullhash=0x7ec5f48701c964fc7563fa5f2dcc52b00f8ad5298942d444f6ce297eeac6c066 contract=0x8EC59dabc0d88eAbeed9cC869B81795Ac321673d
"0x7ec5f48701c964fc7563fa5f2dcc52b00f8ad5298942d444f6ce297eeac6c066"

여기서 가스란?

Gas(가스)는 이더리움이라는 거대한 컴퓨터를 쓰기 위한 일종의 연료이다.

  • 트랜잭션을 수행하는데에 있어 네트워크에 대한 수수료이다.
  1. 스마트 컨트랙트를 배포할 때
  2. 함수에서 상태 변수에 변화를 줄 때

등등 컨트랙트 내부에서 특정 코드를 실행할 때 발생되는 수수료이다.

해결되지 않은 의문…..

가스 계산이 잘못된 것 같다. 내 코드에서는 상태 변수에 변화도 없고, 아주 간단하여 복잡성도 적다..

뭔가 세팅에서 잘못된 것 같은데.. 풀리지 않았다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> eth.getTransactionReceipt(tx)
{
  blockHash: "0xbb1dcbcf02bd1cdc064bfe6027e5021cc80ae9e542282091f9b58297f82f2976",
  blockNumber: 58,
  contractAddress: "0xa9d6fe280d6fbe013726e4f6cfb44871ff2d34b9",
  cumulativeGasUsed: 199384,
  from: "0xc71a5b838cf49fd8bd7db6e1493ae0222f8acf3c",
  gasUsed: 199384,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  status: "0x1",
  to: null,
  transactionHash: "0x587427e8b4ada7cae90cc17dbb1e265df6bb3d5770fdb32db707f89dfbcc7a5e",
  transactionIndex: 0
}

이 트랜잭션에서는 199384의 gas를 사용하였는데, 왜이렇게 많이 사용했을까? 아니면 이게 일반적인 수치인가?

공부가 더 필요하다..

Error: new BigNumber() not a base 16 number:

1
2
3
4
5
6
7
8
9
10
11
> contractInterface.showMsg.call()
Error: new BigNumber() not a base 16 number: 
    at L (bignumber.js:3:2876)
    at bignumber.js:3:8435
    at a (bignumber.js:3:389)
    at web3.js:1110:23
    at web3.js:1634:20
    at web3.js:826:16
    at map (<native code>)
    at web3.js:825:12
    at web3.js:4080:18

뜬금없이 함수 호출할 때, 다음과 같이 Error: new BigNumber() not a base 16 number: 에러가 발생하였다.

구글링해보니 web3.js 버그.. 배포 주소 버그.. 등등에 관련한 질문이 나왔는데 결과적으로는 solidity 컴파일러 버전과 geth 버전의 차이때문에 발생한 문제였다.

geth 버전은 공부하고 있는 책의 버전 대로 1.8.13-stable을, 하지만 solidity 컴파일러는 0.8.x대의 최신 버전을 사용하여 발생한 에러였다.

1
2
% npm uninstall solc
% npm install -g solc@0.4.24

위의 명령어를 통해 컴파일러를 삭제하고, geth 버전에 맞춘 0.4.24 버전으로 다운그레이드하여 해결하였다.

물론, solidity 코드도 pragma solidity = 0.4.24; 로 변경하여 특정 버전의 컴파일러를 선택하도록하였다.

Thanks to.. 힌트 주신 하나의 게시글

This post is licensed under CC BY 4.0 by the author.