본문 바로가기

블록체인

[Solidity/Smart Contract] 솔리디티를 이용한 Smart Contract 배포

이번 블로그에서는 솔리디티 언어를 이용해서 Smart Contract를 배포하는 과정을 해볼 것이다.

먼저 우선적으로 간단한 스마트 컨트랙트의 코드를 솔리디티 언어를 이용해서 만들고, 이를 이더리움 네트워크 안에 트랜잭션을 이용해서 넣은 다음, 네트워크 상에서 이를 실행해보도록 할 것이다.

 

 

  1. 솔리디티
  2. 스마트 컨트랙트
  3. 솔리디티 언어를 이용해서 스마트 컨트랙트 코드 만들기
  4. 이더리움 네트워크와 연결 후 배포

 

 

1. 솔리디티( Solidity )란?

솔리디티는 이더리움 등 블록체인 플랫폼에서 스마트 계약 작성과 구현에 사용되는 계약 지향 프로그래밍 언어이다. 솔리디티는 이더리움 핵심 기여자들에 의해 이더리움과 같은 블록체인 플랫폼상에 스마트 계약을 작성할  수 있도록 개발되었다. 

 

 

 

2. 스마트 컨트랙트( Smart Contract )

 

스마트 계약 또는 스마트 컨트랙트는 계약 당사자가 사전에 협의한 내용을 미리 프로그래밍하여 전자 계약서 문서 안에 넣어두고, 이 계약 조건이 모두 충족되면 자동으로 계약 내용이 실행되도록 하는 시스템이다. 기존의 비트코인이 블록체인 기술을 활용하여 가치의 저장과 전달이 가능한 암호화폐를 만들었다면, 이더리움은 한 단계 더 나아가 블록체인 기술을 활용하여 인간이 상상할 수 있는 모든 종류의 계약을 자동으로 실행할 수 있는 스마트 계약 플랫폼을 개발했다. 스마트 컨트랙트는 이더리움에서 솔리디티 언어로 프로그래밍된다. 

 

솔리디티 언어로 프로그래밍된 스마트 컨트랙트는 컴파일러(solc)에 의해 바이트코드(bytecode)로 컴파일되고, 컴파일된 바이트코드는 블록에 포함되어, 이더리움 가상머신(EVM)에 의해 실행된다.

 

 

 

3. 솔리디티 언어를 이용한 스마트 컨트랙트 코드 만들기 

 

먼저 Vs code에서 Contracts 폴더를 생성 후, 그 안에 HelloWorld.sol 이라는 파일을 생성한다.

 

파일안에 코드 내용은 아래와 같다.

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

// HelloWorld라는 이름을 가진 contract를 가질 것이다.
// 객체지향적인 속성을 가지고 있어서 하나의 contract는 하나의 객체라고 생각하자

contract HelloWorld {
    string text;

    constructor(){
        text = "Hello Wolrd";
    }

    function getText() public view returns(string memory){
        return text;
    }

    function setText(string memory value) public{
        text = value;
    }
}

 

HelloWorld 라는 이름을 가진 contract를 만들고, 우리가 typescript를 통해서 class 객체를 선언하는 것과 같은 방식으로 코드를 구성해야한다.

 

솔리디티의 문법에 대한 것은 추후에 좀 더 자세하게 다뤄볼 예정이니 일단 코드를 저렇게 써보도록 하자.

 

constructor( ) 안에는 text 라는 변수명에 "Hello World"라고 내용을 담아두고 text 변수를 return 해주는 getText( ) 함수와 value의 인자값을 가지고 있는 setText( ) 함수를 만들었다.

 

이렇게 작성한 코드를 EVM이 해석할 수 있도록 컴파일을 거치는 과정이 필요하다. 우리는 컴파일 해주는 것을 도와주는 라이브러리인 'solc' 라이브러리의 도움을 받을 것이다.

 

 

solc

npm init -y
npm install solc
npx solc --bin --abi ./Contracts/HelloWorld.sol // npx solc [디렉토리/파일명]

 

위에 명령어를 입력하면  .abi 와 .bin 파일이 하나씩 생성이 될 것이다.

.abi 파일 안에는 컨트랙트 코드가 JSON 형식으로 들어가있고 .bin 파일은 컨트랙트 코드가 16진수 형식으로 내용이 들어가있다.

 

ABI :

ABI는 Application Binary Interface의 약자로, 스마트 컨트랙트 안에 존재하는 함수와 매개변수들을 JSON 형식으로 나타낸 리스트이다. 우리는 abi를 사용해 컨트랙트 내의 함수를 호출하거나 컨트랙트로부터 데이터를 얻을 수 있다. 실제 스마트 컨트랙트는 바이트 코드로 변환된 후 트랜잭션의 데이터 안에 담겨 이더리움 네트워크 상에 올라간다. 즉, 트랜잭션 데이터 안에 내용을 넣을 때 소스코드를 그대로 넣는 것이 아닌 컴파일된 바이트 코드 형태로 넣어 이더리움 네트워크 상에 올리기 때문에 데이터를 어떻게 전달해야 하는지에 대한 이슈가 발생한다. abi 파일은 사용자가 이더리움 네트워크에 요청한 데이터를 보내줄 때 어떠한 객체 형태로 보내줄 것인지에 대한 결정 파일이다.

 

 

Bin :

16진수 형식으로 바꾼 컨트랙트 바이트코드이다. bin 파일은 기본적으로 실행파일을 의미한다. 컴파일러가 Solidity로 작성된 스마트 컨트랙트 내용을 바이트코드로 변환하여 bin 파일을 만들어준 것이다. 이를 이용해 네트워크상에 컨트랙트를 배포한다.

 

 

정리하자면 스마트 컨트랙트 배포란 이더리움 네트워크 상에 바이트 코드를 올리는 행위를 말한다. 이 때 트랜잭션 객체 안의 data 속성 값에다가 컴파일된 바이트코드인 bin 파일의 내용을 넣어서 이더리움 네트워크에 배포한다. 그리고 abi파일을 이용해서 이더리움 네트워크 상에 올라가있는 컴파일된 스마트 컨트랙트 코드를 호출할 수 있게 된다.

 

 

 

4. 이더리움 네트워크와 연결 후 배포

이제 트랜잭션 객체를 만들어서 컴파일된 스마트 컨트랙트를 이더리움 네트워크와 연결한 후 배포하는 작업을 해보자.

 

이더리움 네트워크를 실행할 때 우리는 평소에 명령어를 이렇게 썼다. 

geth --datadir node --http --http.addr "0.0.0.0" --http.port 9000 --http.corsdomain "\*" \
--http.api "admin,miner,txpool,web3,personal,eth,net" --syncmode full --networkid 7722

 

하지만 여기서 추가로 몇가지를 더 넣어볼 것이다.

--allow-insecure-unlock --unlock "0,1" --password "./node/password"

 

 

위 명령어의 의미는 eth.accounts 에서 0,1번째 인덱스에 해당하는 계정을 가지고 와서 비밀번호를 입력하지 않아도 sendTransaction을 보낼 수 있도록 설정해주는 명령어이다. 그러면 아래의 명령어처럼 이더리움 네트워크를 실행해보도록 하자.

geth --datadir node --http --http.addr "0.0.0.0" --http.port 9000 --http.corsdomain "\*" \
--http.api "admin,miner,txpool,web3,personal,eth,net" --syncmode full --networkid 7722 --allow-insecure-unlock --unlock "0,1" --password "./node/password"

 

 

이더리움 네트워크를 실행하고 Geth console 창도 따로 실행시켜두자.

geth attach http://127.0.0.1:9000

 

 

Geth console 창에서 변수명으로 bytecode 와 abi를 만들고 각각의 내용을 다음과 같이 넣어보자.

bytecode = "0x..."  // bin 파일에 있는 내용들을 넣어주면된다.
abi = [{}]  // abi 폴더에 있는 내용들을 넣어주자

 

 

그리고 트랜잭션을 발생시키이 위한 txObject 객체를 아래와 같이 만들어주자.

txObject={from: eth.coinbase, data:bytecode}

 

스마트 컨트랙트를 배포하기 위한 트랜잭션이므로 속성값으로 트랜잭션을 발생시키는 계정을 from에 넣어주고 스마트 컨트랙의 내용이 담긴 bytecode를 data 속성에 넣어준다. 

 

자 이제 sendTransaction을 해보자

eth.sendTransaction(txObject)

 

 

트랜잭션을 보내면 return 값으로 Txhash 값이 나오고 이 값을 eth.getTransaction() 메소드의 인자값으로 전달해 txpool에 들어간 트랜잭션 내용을 조회하면 다음과 같이 나온다

> eth.getTransaction("0x44b6384d348486a8a5921dd40c47b7804e8309ad9fc451b34462f56a7ce114c5")
{
  blockHash: null,
  blockNumber: null,
  from: "0x014c07eaddde278a1a3ad93a8c7b0c530bc90961",
  gas: 447229,
  gasPrice: 1000000000,
  hash: "0x44b6384d348486a8a5921dd40c47b7804e8309ad9fc451b34462f56a7ce114c5",
  input: "0x60806040523480156200001157600080fd5b506040518060400160405280600b81526020017f48656c6c6f20576f6c726400000000000000000000000000000000000000000081525060009081620000589190620002d9565b50620003c0565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620000e157607f821691505b602082108103620000f757620000f662000099565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620001617fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000122565b6200016d868362000122565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620001ba620001b4620001ae8462000185565b6200018f565b62000185565b9050919050565b6000819050919050565b620001d68362000199565b620001ee620001e582620001c1565b8484546200012f565b825550505050565b600090565b62000205620001f6565b62000212818484620001cb565b505050565b5b818110156200023a576200022e600082620001fb565b60018101905062000218565b5050565b601f82111562000289576200025381620000fd565b6200025e8462000112565b810160208510156200026e578190505b620002866200027d8562000112565b83018262000217565b50505b505050565b600082821c905092915050565b6000620002ae600019846008026200028e565b1980831691505092915050565b6000620002c983836200029b565b9150826002028217905092915050565b620002e4826200005f565b67ffffffffffffffff8111156200030057620002ff6200006a565b5b6200030c8254620000c8565b620003198282856200023e565b600060209050601f8311600181146200035157600084156200033c578287015190505b620003488582620002bb565b865550620003b8565b601f1984166200036186620000fd565b60005b828110156200038b5784890151825560018201915060208501945060208101905062000364565b86831015620003ab5784890151620003a7601f8916826200029b565b8355505b6001600288020188555050505b505050505050565b61068580620003d06000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80635d3a1f9d1461003b578063e00fe2eb14610057575b600080fd5b61005560048036038101906100509190610274565b610075565b005b61005f610088565b60405161006c9190610345565b60405180910390f35b8060009081610084919061057d565b5050565b60606000805461009790610396565b80601f01602080910402602001604051908101604052809291908181526020018280546100c390610396565b80156101105780601f106100e557610100808354040283529160200191610110565b820191906000526020600020905b8154815290600101906020018083116100f357829003601f168201915b5050505050905090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61018182610138565b810181811067ffffffffffffffff821117156101a05761019f610149565b5b80604052505050565b60006101b361011a565b90506101bf8282610178565b919050565b600067ffffffffffffffff8211156101df576101de610149565b5b6101e882610138565b9050602081019050919050565b82818337600083830152505050565b6000610217610212846101c4565b6101a9565b90508281526020810184848401111561023357610232610133565b5b61023e8482856101f5565b509392505050565b600082601f83011261025b5761025a61012e565b5b813561026b848260208601610204565b91505092915050565b60006020828403121561028a57610289610124565b5b600082013567ffffffffffffffff8111156102a8576102a7610129565b5b6102b484828501610246565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156102f75780820151818401526020810190506102dc565b83811115610306576000848401525b50505050565b6000610317826102bd565b61032181856102c8565b93506103318185602086016102d9565b61033a81610138565b840191505092915050565b6000602082019050818103600083015261035f818461030c565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806103ae57607f821691505b6020821081036103c1576103c0610367565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026104297fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826103ec565b61043386836103ec565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b600061047a6104756104708461044b565b610455565b61044b565b9050919050565b6000819050919050565b6104948361045f565b6104a86104a082610481565b8484546103f9565b825550505050565b600090565b6104bd6104b0565b6104c881848461048b565b505050565b5b818110156104ec576104e16000826104b5565b6001810190506104ce565b5050565b601f82111561053157610502816103c7565b61050b846103dc565b8101602085101561051a578190505b61052e610526856103dc565b8301826104cd565b50505b505050565b600082821c905092915050565b600061055460001984600802610536565b1980831691505092915050565b600061056d8383610543565b9150826002028217905092915050565b610586826102bd565b67ffffffffffffffff81111561059f5761059e610149565b5b6105a98254610396565b6105b48282856104f0565b600060209050601f8311600181146105e757600084156105d5578287015190505b6105df8582610561565b865550610647565b601f1984166105f5866103c7565b60005b8281101561061d578489015182556001820191506020850194506020810190506105f8565b8683101561063a5784890151610636601f891682610543565b8355505b6001600288020188555050505b50505050505056fea2646970667358221220f615be3b856b1030d532329d3267bf250a056dc8c70c70f4411547e95d2b65fb64736f6c634300080f0033",
  nonce: 13,
  r: "0xce940acdb3c9f9ae81a1cf9e7a303992d0e3829abaae689c41df7c126bae41e7",
  s: "0x6bda6868d29bc0899fe56644221caf7180eb9773e9fa49b9f2d34273fa2695a3",
  to: null,
  transactionIndex: null,
  type: "0x0",
  v: "0x3c78",
  value: 0
}

 

트랜잭션 객체의 input 속성값으로 스마트 컨트랙트를 컴파일 한 바이트 코드가 들어가있는 것을 확인할 수 있다. 여기서 miner.start() 메소드를 사용해서 마이닝을 진행하면 이더리움 네트워크 상에 스마트 컨트랙트 배포가 완료된다.

 

 

 

스마트 컨트랙트 실행

위에 과정들을 통해서 이더리움 네트워크에 스마트 컨트랙트를 배포하는 작업을 했다. 이제 배포한 스마트 컨트랙트를

실행시키기 위해서 CA(Contract Address) 를 구해보자

 

eth.getTransactinoReceipt(transactionHash)
> eth.getTransactionReceipt("0x44b6384d348486a8a5921dd40c47b7804e8309ad9fc451b34462f56a7ce114c5")
{
  blockHash: "0x724bf48b982fa5614b1d4b5869f365665661448136b35a1a389fc4914febaebd",
  blockNumber: 24,
  contractAddress: "0x45b0febdebc18205dd125c68dc303995ec31bcdb",
  cumulativeGasUsed: 447229,
  effectiveGasPrice: 1000000000,
  from: "0x014c07eaddde278a1a3ad93a8c7b0c530bc90961",
  gasUsed: 447229,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  status: "0x1",
  to: null,
  transactionHash: "0x44b6384d348486a8a5921dd40c47b7804e8309ad9fc451b34462f56a7ce114c5",
  transactionIndex: 0,
  type: "0x0"
}

위에 내용에 나온 것처럼 contractAddress에 대한 값이 나왔다. 스마트 컨트랙트를 실행시키기 위해선 CA를 이용해야한다. CA는 스마트 컨트랙트가 배포되었을 때 생성되는 계정으로 스마트 컨트랙트의 고유한 키값이다. 

 

 

이제 Geth console을 이용해서 contract 변수를 할당하자

contract = eth.contract(abi)

eth.contract의 인자값으로 우리가 변수로 설정했던 abi값을 넣어준다. contract의 내용을 살펴보면 contract 객체에 abi 함수들이 추가된 것을 확인할 수 있다.

 

[{
    inputs: [],
    stateMutability: "nonpayable",
    type: "constructor"
}, {
    inputs: [],
    name: "getText",
    outputs: [{
        internalType: "string",
        name: "",
        type: "string"
    }],
    stateMutability: "view",
    type: "function"
}, {
    inputs: [{
        internalType: "string",
        name: "value",
        type: "string"
    }],
    name: "setText",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function"
}]
> contract = eth.contract(abi)
{
  abi: [{
      inputs: [],
      stateMutability: "nonpayable",
      type: "constructor"
  }, {
      inputs: [],
      name: "getText",
      outputs: [{...}],
      stateMutability: "view",
      type: "function"
  }, {
      inputs: [{...}],
      name: "setText",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function"
  }],
  eth: {
    accounts: ["0x014c07eaddde278a1a3ad93a8c7b0c530bc90961", "0x577bf8bc56b26a00e21a57b999421e002a8f5979", "0x76841c2fb041c364de4461969256df233a08264a", "0x4019cef0734782f1adf3119fa0c208e2d8bf6f84"],
    blockNumber: 24,
    coinbase: "0x014c07eaddde278a1a3ad93a8c7b0c530bc90961",
    compile: {
      lll: function(),
      serpent: function(),
      solidity: function()
    },
    
  },
  at: function(address, callback),
  getData: function(),
  new: function()
}

 

 

이제 instance라는 변수명에 contract.at(CA)라는 내용을 할당하자.

instance = contract.at(CA)
 instance = contract.at("0x45b0febdebc18205dd125c68dc303995ec31bcdb")
{
  abi: [{
      inputs: [],
      stateMutability: "nonpayable",
      type: "constructor"
  }, {
      inputs: [],
      name: "getText",
      outputs: [{...}],
      stateMutability: "view",
      type: "function"
  }, {
      inputs: [{...}],
      name: "setText",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function"
  }],
  address: "0x45b0febdebc18205dd125c68dc303995ec31bcdb",
  transactionHash: null,
  allEvents: function bound(),
  getText: function bound(),
  setText: function bound()
}

instance 객체 안에는 abi 파일의 내용과 스마트 컨트랙트 안에서 작성된 함수들이 들어있는 것을 확인할 수 있다 

-> getText, setText

 

블록이 마이닝될 때 EVM은 트랜잭션 객체의 data 속성값으로 들어간 바이트 코드를 실행시키고 해당 컨트랙트 코드를 토대로 인스턴스를 생성하게 된다. CA를 통해서 컨트랙트를 배포한 내용을 찾을 수 있고 abi를 통해서 함수를 호출할 수 있다.

 

instance 객체에서 getText 함수를 실행시키기 위해 아래의 명령어를 작성하면 된다

instance.getText.call()

-> "Hello Wolrd"

 

 

이번엔 스마트 컨트랙트에 있는 상태변수를 바꿔줄 수 있는 함수를 사용할 것이다 -> setText()

 

전에 사용했던 eth.sendTransaction(txObject)가 아닌 contract.new(txObject) 메소드를 사용해서 스마트 컨트랙트를 배포할 것이다. miner.start() 메소드를 통해 txpool에 담긴 트랜잭션 객체를 블록에 담아 이더리움 네트워크에 배포한 다음 instance 객체를 사용해 setText 함수를 사용했다.

 

txObject ={from:eth.coinbase, data:bytecode}

contract=eth.contract(abi)

instance = contract.new(txObject)

miner.start(1)
miner.stop()

instance.setText('hello Contract',{from:eth.coinbase})

 

setText() 함수의 두번째 인자값으로 from:eth.coinbase 객체를 전달했다. 전달한 이유는 우리가 트랜잭션 안에 있는 상태변수를 바꾸고자 할 때 수수료를 지불해야한다(가스비) 때문에 트랜잭션을 발생시킬 계정의 정보를 인자값으로 같이 전달해줘야 한다. 

 

트랜잭션이 발생한 다음 다시 한번더 마이닝 작업을 통해 블록을 생성하고 instance.getText.call() 메소드를 입력하면 우리가 바꿧던 hello Contract가 뜨는 것을 확인할 수 있다.