[BlockChain]이더리움 블록체인_web3.js 스마트컨트랙트 실행하기

4 minute read

이번 포스팅은 Web3.js를 통해서 스마트컨트랙트를 실행하는 방법을 포스팅하겠습니다. 스마트컨트랙트를 실행하기에 앞서서 우선 Solidity란 무엇이고 어떻게 작성해야 되는지에 대해서 알아보겠습니다.

Solidity

솔리디티는 JAVA의 객체지향과 매우 비슷한 구조라고 생각하시면 됩니다. 여기서 JAVA의 객체지향을 모르신다면 기본적으로 객체지향에 대해서는 알고있어야 기본적으로 solidity를 아실수 있을거에요.

pragma solidity ^0.4.8;

contract ADContract {

  //광고를 계약한 주소
  address public contract_address;  //계약소유자 주소
  string url;
  string subject;
  string descript;

  //계좌잔액
  mapping (address => uint) public balanceOf; //계좌잔액

  //광고를 통한 송금
  //광고 계약등록 -> 광고를 등록하면 계약이 실행됨
  function ADContract(string _url, string _subject, string _descript, uint _value) public {
      balanceOf[msg.sender] = _value;  //해당 계약한 주소에 이더의 개수를 저장한다.
      contract_address = msg.sender;
      url = _url;
      subject = _subject;
      descript = _descript;
  }
  //스마트 컨트랙트를 통한광고 각각의 정보 얻기
  function getURL() returns (string){
      return url;
  }
  function getSubejct() returns (string){
      return subject;
  }
  function getDescript() returns (string){
      return descript;
  }
  //계약한 주소 얻기
  function getAddress() returns (address){
      return msg.sender;
  }
  //계약리스트 얻기
  function getContract() returns (string, string, string)
  {
    return (url, subject, descript);
  }
  //송금을 이용한 contract 실행 트랜잭션 실행
  function transfer(address _to, uint _value) payable {
    //계약을 등록한 contract주소 잔액을 감소시키고 해당 등록한 사용자의 contract의 잔액이 증가한다.
    balanceOf[contract_address] -= _value;
    balanceOf[_to]  += _value;  //해당 광고를 본 사람에게는 잔액이 증가한다.
  }
  //자신의 현재 토큰 광고를 등록했던 잔액을 나타낸다. 각토큰의 개수 -> 자신의 토큰개수를 알수 있다.
  function getBalnace(address _account) constant returns(uint){
      return balanceOf[_account];
  }
  //자신의 부족한 토큰을 이더리움으로 채운다.
  function deposit(address _from, uint _value){
    //해당광고에 대해서 토큰을 채운다.
    balanceOf[_from] += _value;
  }
  //해당 광고에 이더가 남아있는지 체크한다.
  function checkToken(address _from) returns (bool){
      if(balanceOf[_from] > 0){
          return true;
      }
      return false;
  }
}

Solidity은 Contract라는 클래스 파일과 여러개의 기능을 하는 Function으로 나누어집니다. 객체지향처럼 클래스 안에 변수를 선언하는 것처럼 컨트랙트에 올라갈 변수를 작성하죠.

위 컨트랙트는 광고컨트랙트로 해당 광고에 대한 정보를 블록체인에 올라가는 Solidity 언어입니다. 잠깐 변수타입에 대해서 간단히 알아보죠. 참고로 나머지 변수들은 JAVA를 아신다면 충분히 아실 수 있는 내용이라 생략하겠습니다.

  • address : address는 사전정의 그대로 블록체인으로 생성된 account들을 저장하는 변수입니다.

  • mapping : mapping ( KeyType => ValueType ) public name; 형식이며 맵과 사전과 같은 자료구조로서 키와 값을 매핑하기 위해 사용합니다.

(ex : mapping(address => uint) public Ethereum 이면 Ethereum[0], Ethereum[1] 같이 배열로 사용할 수 있습니다.)

  • msg.sender : 스마트컨트랙트를 실행하는 Address(이더리움 계좌) 입니다.

스마트컨트랙트

Web3로 스마트컨트랙트를 실행하는 방법은 컨트랙트를 Deploy하고 그 값을 abi코드로 블록체인에 올리면 됩니다. 우리가 할 스마트컨트랙트는 매우 간단한 코드입니다.

pragma solidity ^0.4.18;

contract HelloWorld{
  string public greeting;
  function HelloWorld(){
    greeting = "Hello, World";
  }
  function say() returns (string){
    return greeting;
  }
}

간단하게 변수에 Hello World를 할당하고 say() Call을 이용해서 직접 Hello World를 출력해봅시다. 저는 HelloWorld.sol파일을 /contracts/HelloWorld.sol 경로에 저장하였습니다.

스마트컨트랙트 컴파일

var solc = require('solc'); //contract Compile
var fs = require('fs'); //file 시스템

app.get('/smartcontract', function(req,res){
  //File Read
  var source = fs.readFileSync("./contracts/HelloWorld.sol", "utf8");

  console.log('transaction...compiling contract .....');
  let compiledContract = solc.compile(source);
  console.log('done!!' + compiledContract);

  var bytecode = '';
  var abi = '';
  for (let contractName in compiledContract.contracts) {
     // code and ABI that are needed by web3
     abi = JSON.parse(compiledContract.contracts[contractName].interface);
     bytecode = compiledContract.contracts[contractName].bytecode; //컨트랙트 생성시 바이트코드로 등록
     // contjson파일을 저장
  }
})

Solc모듈과 fs모듈을 이용하여 해당 스마트컨트랙트를 컴파일 합니다. solc은 solidity를 JSON형태로 컴파일 해주는 모듈입니다. 즉 solc로 컴파일된 JSON파일을 abi와 bytecode로 나누어서 저장해야 합니다.

JavaScript에는 JSON.parse라는 함수와 bytecode로 변환시켜주는 함수가 내장되어 있습니다. 이를 잘 활용해서 위 코드처럼 작성해주시면 됩니다.

스마트컨트랙트 Deploy

const MyContract = new web3.eth.Contract(abi);

let deploy = MyContract.deploy({
               data: bytecode,
               from: send_account}).encodeABI();

제일먼저 web3.eth.Contract를 통해서 객체를 생성해야 합니다. 단 이때 변수 값은 abi가 들어가야 합니다. (Deploy를 하기 위해서는 객체 생성시 abi만 변수로 넣으면 됩니다.) Deploy는 위에서 컴파일한 bytecode와 스마트컨트랙트를 실행할 account를 적어주세요. 그리고 이 값을 ABI값으로 변경하면 됩니다. (여기서 GasLimit와 GasPrice은 제 임의로 지정하였습니다.)

const txObject = {
  nonce:    web3.utils.toHex(txCount),
  gasLimit: web3.utils.toHex(1000000), // Raise the gas limit to a much higher amount
  gasPrice: web3.utils.toHex(web3.utils.toWei('10', 'gwei')),
  data : '0x' + deploy  // <- Hex 기반으로 해야되는 것 주의!!
};

그리고 getTransactionCount 함수를 사용하여 Nonce값을 구하고, Object의 data에는 ABI값이 저장된 deploy로 할당하면 됩니다.(단 여기서 중요한 것은 abi로 변환한 값을 hex값 형식으로 변환해야합니다. 때문에 ‘0x’를 앞에다가 붙여준 것이에요)이 Object를 web3.eth.sendSignedTransaction 함수를 통해서 트랜잭션을 이더리움블록체인에 실행시키면됩니다.

Image Alt 텍스트

이처럼 contractAddress가 이상없이 생성됬다면 스마트컨트랙트가 이더리움 블록체인에 정상적으로 등록된 것입니다. 실제 스마트컨트랙트의 함수를 Call할 때에도 이 contractAddress만 있으면 언제든지 call이 가능하죠. 트랜잭션Hash 로그는 https://ropsten.etherscan.io/tx/transactionhash값에서 확인할 수 있습니다.

스마트컨트랙트 실행

const MyContract = new web3.eth.Contract(abi, '0x7195Aa901170fA6D3FcECEce1832B54a6872a976');
MyContract.methods.say().call().then(result=>console.log("smartcontract Call :" + result));

컨트랙트 실행시에는 객체 생성 시 변수값에 위 Contract Address값을 넣어주고 객체를 생성해줍니다. 그리고 그 객체를 Call 해주면 이상없이 호출 되는 것을 볼 수 있죠. 중요한 것은 앞서 스마트컨트랙트를 실행시킨 트랜잭션 Hash값을 알고 있어야 된다는 점입니다. 때문에 DAPP을 개발하게 되면 Contract Address를 어떻게 관리할지에 대한 고민이 필요합니다.

Image Alt 텍스트

var express = require('express');
var app = express();
var Web3 = require('web3');
var web3 = new Web3(new Web3.providers.WebsocketProvider('wss://ropsten.infura.io/ws'));
var Tx = require('ethereumjs-tx');

const send_account    = "0x82Ff486364ebDbEe1b34b7538ceD72448d9b38cb";
const receive_account = "0xf2a58d3F268642f7f5416F4248f4Fb2623a5D929";
const privateKey = Buffer.from('privateKey', 'hex');


var solc = require('solc'); //contract Compile
var fs = require('fs'); //file 시스템

app.get('/smartcontract', function(req,res){
  //File Read
  var source = fs.readFileSync("./contracts/HelloWorld.sol", "utf8");

  console.log('transaction...compiling contract .....');
  let compiledContract = solc.compile(source);
  console.log('done!!' + compiledContract);

  var bytecode = '';
  var abi = '';
  for (let contractName in compiledContract.contracts) {
     // code and ABI that are needed by web3
     abi = JSON.parse(compiledContract.contracts[contractName].interface);
     bytecode = compiledContract.contracts[contractName].bytecode; //컨트랙트 생성시 바이트코드로 등록
     // contjson파일을 저장
  }
  console.log(abi);

  const MyContract = new web3.eth.Contract(abi);

  let deploy = MyContract.deploy({
                 data: bytecode,
                 from: send_account}).encodeABI();
  //deploy
  web3.eth.getTransactionCount(send_account, (err, txCount) => {

  const txObject = {
    nonce:    web3.utils.toHex(txCount),
    gasLimit: web3.utils.toHex(1000000), // Raise the gas limit to a much higher amount
    gasPrice: web3.utils.toHex(web3.utils.toWei('10', 'gwei')),
    data : '0x' + deploy
  };

  const tx = new Tx(txObject);
  tx.sign(privateKey);

  const serializedTx = tx.serialize();
  const raw = '0x' + serializedTx.toString('hex');

  web3.eth.sendSignedTransaction(raw)
     .once('transactionHash', (hash) => {
       console.info('transactionHash', 'https://ropsten.etherscan.io/tx/' + hash);
     })
     .once('receipt', (receipt) => {
       console.info('receipt', receipt);
    }).on('error', console.error);
  });

});

//app을 listen
app.listen(4000, function(){
  console.log('Connected BlockChain, 4000 port!');
});