본문 바로가기
개발공부일지/Block-Chain

Ethereum - Smart-Contract(2) : 기본 코드 작성하기

by Hynn1429 2023. 5. 31.
반응형

안녕하세요.

Hynn 입니다.

 

이번 포스팅에서는 Smart-Contract 기본적 코드를 작성하여, 어떠한 흐름으로 동작하는지를 알아보도록 하겠습니다.

이번 포스팅에서는 각 디렉토리의 기초코드 및, Console 에 대한 기본 이해를 구성해보도록 하겠습니다.

 

=======================

1. Contract 

2. Truffle.config

3. Migrations

4. Truffle Test

5. Console 기본 사용하기

6. Build

=======================

 

1. Contract 

 

먼저 Contract 에서는 "Solidity" 코드를 작성해야 합니다.

기존의 JavaScript 와는 다른 문법을 가지고 있기 때문에, 이를 기초적으로 알아보도록 하겠습니다.

 

먼저 사용할 Solidity 의 버전을 선언해야 합니다.

 

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

 

기본적인 예제 코드에서는 0.8.0 으로 작성하도록 하겠습니다.

여기서 버전을 선언하기 위해서 "prgma solidity" 를 작성하고 ^뒤에 버전의 대한 정보를 기입하고, " ; " 으로 줄마다 표시해야 합니다. 

Solidity 는 " ; " 이 필수적으로 작성되어야 합니다.

 

즉 , Pragma 는 C# 같은 곳에서와 같이, Solidity 코드의 컴파일러 버전을 명시하는 지시문입니다. 이외에도 몇가지 지시문이 존재하지만, 기본 예제 코드에서는 "solidity" 를 사용하여, 버전을 명시하였습니다.

 

이제, Javascript 에서 주로 사용되었던 "Class" 와 같이 객체 지향 프로그래밍의 구현을 위해, Solidity 에서는 "Contract" 의 생성자를 작성해야 합니다.

 

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

contract Test {
    address public app;
    
    constructor() {
        app = msg.sender;
    }

    function getValue() public pure returns (uint256 value) {
        return 5;
    }
}

먼저 위 예제 코드를 살펴보면 Test 라는 Smart-Contract 을 "contract Test" 라고 정의하겠음을 선언한 것입니다. 

항목 내에서는 먼저 app 이라는 public 변수를 선언하였습니다. public 은 누구나 이 변수에 접근할 수 있도록 선언한 것이며, "address" 라는 타입으로 선언되었고, 이 타입은 주로 Ethereum 주소를 저장하기 위해 사용됩니다. 

참고를 위해 주요 변수타입은 아래와 같습니다. 

 

  • address - 20바이트 크기의 Ethereum 주소를 저장할 때 사용됩니다.
  • Integer (uint or int 8~256) - 정수를 표현하기 위해 uint 혹은 int 로 표현합니다. 여기서 "uint" 는 부호가 없는 정수를 뜻하고, "int" 는 부호가 있는 정수를 뜻합니다. 뒤의 숫자는 8바이트부터 256바이트까지의 크기를 가질 수 있다는 의미를 부여합니다. 즉 8바이트부터 256까지의 부호가 있거나 없는 정수의 데이터 타입을 뜻합니다.
  • Boolean - 이 타입은 다른 언어와 마찬가지로 "True" , "False" 의 타입을 가집니다.
  • Fixed-size byte arrays(bytes1~32) - 고정 크기 Byte 배열을 작성할 때 사용합니다. 즉 bytes1 로 설정하면 1바이트의 고정 배열로 타입이 결정되고 최대 32바이트까지 지정할 수 있습니다.
  • Dynamic-size byte array (bytes, string) - 위의 고정크기 Bytes 배열과 다르게 동적 크기의 바이트 배열입니다. 여기서 Bytes 는 임의의 길이의 바이트 시퀸스를 뜻하며, "string" 은 유니코드 문자열을 나타냅니다.
  • Array(uint[], bool[5]) - 단어 그대로 배열을 의미합니다. 예제에 있는 uint[] 는 동적 크기의 부호없는 정수를 나타내며, 만약 배열을 의미하는 []내에 bool과 같이 특정 숫자를 지정하면, 동적이 아니라 고정 크기의 배열타입으로 정의됩니다.
  • Enums - Enums 은 사전에 정의한 한정된 값을 가질 수 있는 데이터 타입입니다.  즉 작성자가 1~12 만을 정의했다면 이 데이터 타입에는 1~12 값만 가질 수 있습니다. 일반적으로 좋은 예제는 회원가입 작성시 1월~12월과 같은 옵션만을 선택할 수 있는 타입입니다.
  • Structs - 여러 변수를 하나의 복합 데이터 타입으로 묶는데 사용됩니다. 예를들어, Structs 를 사용하면, 객체형태의 데이터 타입을 지정하는 것과 유사합니다. 
  • Mapping - Key:Value 의 데이터 타입을 지정할 때 사용합니다. 
  • Function Type - 함수 타입으로 데이터 타입을 지정할 때 사용합니다.

 

이제 class 생성자 처럼 "constructor" 를 사용하여 생성자 함수를 작성할 수 있습니다. 위의 예제에서는 app 이라는 변수에 msg.sender 로 변수를 초기화합니다. 여기서 msg.sender 는 현재 함수를 호출한 주소를 의미합니다. 

 

각 method 는 올바른 순서에 맞게 작성해야 합니다. 위의 예제에서는 "function getValue() public pure returns (uint256 value) 라는 형태로 작성이 되었습니다.

먼저 function 으로 함수임을 선언하고, getValue() 라는 함수명칭을 작성하고 public, 즉 누구나 접근이 가능한 함수라는 의미를 부여하고, pure 키워드를 작성했습니다. 이 키워드는 크게 3가지의 종류가 존재합니다.

 

  • pure - 함수의 계약의 저장소를 읽지도, 수정하지도 않음을 의미합니다. 
  • view - 함수의 계약의 저장소를 읽을 수 있지만, 변경을 할 수는 없음을 의미합니다.
  • payable - 함수의 계약이 Ether 를 받을 수 있습니다.

이 내용에 대해서는 이후에 실제 코드를 작성시, 각 요소가 갖는 세부적인 의미를 알아보도록 하겠습니다.

 

그리고 난 뒤, "returns" 를 지정하고, 반환되는 값이 uint256 value 라는 것을 명시합니다. 즉 여기서는 함수가 반환하는 값이 부호가 없는 정수가 최대 256바이트를 가진 값이 될 것임을 명시하였습니다.

즉, 실제 함수 내에서도 return 값은 5로 명시가 되어 있습니다.

이러한 코드가 Contract 에서 기본적으로 사용하는 코드입니다.

 

2. Truffle Config

 

먼저 테스트를 위해서는, config 내의 몇가지 설정을 구성해야 합니다.

먼저 네트워크 탭에서 아래의 주석을 해제하여, 설정을 구성해야 합니다. 당연히 Ganache-Cli 는 실행이 되어 있어야 합니다.

 

  networks: {
    development: {
      host: "127.0.0.1", // Localhost (default: none)
      port: 8545, // Standard Ethereum port (default: none)
      network_id: "*", // Any network (default: none)
    }
  }

위 사항에 대한 설명을 추가하자면, Ganache-Cli 를 터미널에서 실행하면 가장하단에 실제 실행된 주소와 포트가 표시됩니다.

Listening on 127.0.0.1:8545

즉 , 개발환경의 기본 네트워크 설정이 이미 Truffle 에 주석으로 제공이되고, network_id 의 * 표시는 모든 네트워크를 의미합니다.

 

그리고 Solidity 의 버전을 예시코드에서는 0.8.0 으로 설정했기 때문에, "compilers" 에서 아래와 같이 수정이 필요합니다.

 

compilers: {
    solc: {
      version: "0.8.0", // 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"
      // }
    },
  },

현재는 docker 와 같은 추가환경을 구성하지 않았으므로, 버전에 대해서만 수정이 필요합니다.

이외의 설정에 대해서는 추후 다루도록 하겠습니다.

이렇게 설정하면 기본적인 Truffle Config 설정이 완료됩니다.

 

3. Migrations

 

이제 작성한 파일을 Compile 을 진행하기 위한 코드를 작성해야 합니다.

이전 포스팅에서 설명드린것 처럼 컴파일을 진행하기 위한 코드를 정의하는 파일은 컴파일의 순서대로 숫자를 접두사에 작성해야 합니다.

예제에서는 "01_deploy.app.js" 와 같은 형태로 숫자를 접두사로 작성해야 합니다.

const App = artifacts.require("App");

module.exports = (deployed) => {
  deployed.deploy(App);
};

예제 코드를 살펴보도록 하겠습니다.

먼저 기존의 ES6, Node 환경과 같이 작성된 Smart-Contract 을 불러와야 합니다. 즉, const App 에는 "Artifacts.require" 를 사용하여, Smart-Contract 를 가져와야 하며, 괄호 내에 작성된 명칭은 contract 에서 사용한 선언명칭을 그대로 입력해야 합니다. 즉 App 으로 입력한 Smart-Contract 은 App 으로 지정해야 합니다.

 

그리고 난 뒤, module.exports 를 사용하여 Truffle 에서 Smart-Contract 을 배포하는 코드를 작성해야 합니다. 여기서 사용되는 deployed 매개변수는 Truffle 에서 제공되는 deployer 객체를 의미합니다. 즉, deployed.deploy 메서드는 Smart-Contract 을 Ethereum 네트워크에 배포하는 작업을 수행합니다.

 

즉, App 이라고 작성한 Solidity Smart-Contract 코드를 App 이라는 변수에 가져온 뒤, module.exports 를 사용하여 배포하도록 지시하는 코드를 작성한 예시입니다.

이를 작성하고 아래의 명령어를 터미널에 입력합니다.

 

truffle compile

코드를 올바르게 작성했다면, 아래의 내용이 터미널에 표시되면서, 컴파일이 완료된 것을 볼 수 있습니다.

truffle compile

Compiling your contracts...
===========================
> Compiling ./contracts/app.sol
> Artifacts written to /Users/hynn/Desktop/Block-Chain/Dapp/build/contracts
> Compiled successfully using:
   - solc: 0.8.20+commit.a1b79de6.Emscripten.clang

이를 완료했다면, Build 라는 디렉토리 내에 app.json 파일이 생성되며 컴파일이 완료됩니다. 

 

 

4. Truffle Test 

 

먼저 테스트 코드 작성을 위해 test directory 에 테스트용 파일을 생성합니다.

제 경우에는 Contract File 명과 동일하게 설정 후 test 를 붙여 파일을 생성합니다. 예를들어, Contract 에서 테스트를 하고자 하는 파일의 명칭이 "App.sol" 일 경우, "App.test.js" 로 작성합니다.

 

여기서도 테스트 코드를 작성하기 위해서는, Migrations 과 마찬가지로 Artifacts.require 를 사용하여 테스트를 할 Contract 코드를 가져와야 합니다.

 

그리고 난 뒤, 기존의 JavaScript 형태로 코드를 작성할 수 있습니다.

 

const Lottery = artifacts.require("App");

// Truffle Test Most TDD Tools Mocha

contract(
  "app",
  function ([
    deployer,
    user1,
    user2,
    user3,
    user4,
    user5,
    user6,
    user7,
    user8,
    user9,
    user10,
  ]) {
    let app;
    beforeEach(async () => {
      console.log("beforeEach");

      app = await App.new();
    });

    it("basic test", async () => {
      console.log("basic test");
      let owner = await app.owner();
      let value = await app.getValue();
      console.log(`owner is ${owner}`);
      console.log(`value is ${value}`);
      assert.equal(value, 5);
    });
  }
);

위의 테스트 코드를 살펴보면, contract 내에 먼저 테스트코드의 명칭을 작성 후 , function 을 선언합니다.

deployer 는 contract address, 그리고 user1~10 은 ganache-cli 에서 생성된 10개의 계정을 의미합니다.

 

그리고 let 을 사용하여 app 이라는 변수를 선언합니다.

그리고 beforeEach 를 async/await 함수로 작성을 하고, await 을 사용하여 App.new() 를 사용해, 테스트 코드 내에서 새롭게 인스턴스를 생성하도록 작성합니다.

즉 beforeEach 의 역활은 기존의 Javascript 와 마찬가지로 각 테스트별로 인스턴스의 상태가 공유되지 않도록 인스턴스를 생성하는 역활을 담당합니다.

 

그리고 It 메서드에서 각 테스트를 위해 메서드를 사용하여 함수를 호출하고 반환값이 올바른지를 테스트하는 기본 코드를 작성합니다.

이 코드의 메서드를 이해하기 위해서는 truffle console 을 같이 살펴보면 좋습니다.

 

이 테스트 코드를 실행하기 위해서는 기본적으로 아래의 명령어를 사용할 수 있습니다.

 

truffle test
truffle test/app.test.js

첫번째 명령어는 전체 테스트 파일을 실행하는 명령이며, 두번째 명령어는 app.test.js 특정 파일의 테스트를 수행합니다.

이제 console 에서 위에서 사용된 메서드들이 어떠한 경로로 실행되는지 알아보도록 하겠습니다.

 

5. Console 기본 사용하기

 

Truffle console 에서는 이제 세부적인 method 를 살펴볼 수 있습니다.

Smart-Contract 에서는 컴파일을 수행하면, json 파일을 생성하면서, 다양한 method 를 사용할 수 있습니다.

 

먼저 가장 기초가 되는것은 "web3" 라는 method 를 실행하면 다양한 메서드를 확인할 수 있습니다. 

이를 확인하기 위해서는 "truffle console" 을 실행합니다.

 

이 실행되는 Directory 는 당연하게 truffle 프로젝트를 생성한 위치에서 실행해야 합니다.

 

그리고 난뒤 Truffle console 을 실행하면 터미널이 아래와 같이 인터페이스가 표시됩니다.

 

이제 위에서 web3 를 입력 후 실행하면 수많은 method 가 표시됩니다. 

 

위 내용은 실제 컴파일이 완료된 ".JSON" 파일 내의 구성된 다양한 항목들이 표시됩니다.

여기서 주로 사용되는 method 인 "eth" 를 이용해 단계별로 위에서 사용한 테스트코드에 언급된 메서드를 표시해보겠습니다.

 

먼저 eth 를 쉽게 사용하기 위해서 아래와 같은 형태로 eth 라는 변수에 web3.eth 를 담아보도록 하겠습니다.

let eth = web3.eth

이렇게 한 뒤, eth 를 입력하면 "eth" 내에 존재하는 메서드가 표시됩니다.

Console 에서 존재하는 method 목록을 표시하려면 "eth. " 을 입력 후 탭을 클릭하면 아래와 같은 예제로 표시됩니다.

위의 예제가 바로, 실제 테스트코드에서 사용한 다양한 method 입니다. Truffle 을 사용하여 컴파일을 수행하면, 위와 같이 다양한 메서드를 사용할 수 있습니다. 메서드의 전체 목록을 설명드리기는 어렵지만, 위의 리스트에서 필요한 메서드를 찾아 실행하여 다양한 기능을 구현할 수 있겠습니다.

 

6. Build

 

이제 Build 를 하여 실제 Ganache 에 위의 코드를 배포할 수 있습니다.

이 배포에 대해서는 주의해야 할 사항이 있습니다.

먼저 명령어 두개에 대해서 차이점을 이해해야 이를 쉽게 파악할 수 있습니다.

 

truffle migrate
truffle migrate --reset

 

최초에는 truffle migrate 만해도 관계가 없습니다.

하지만, 코드를 수정 후 다시 배포하게 되는 경우, "--reset" 을 입력하지 않으면, 기존의 코드와 수정된 코드의 컴파일 및 배포가 올바르게 이루어지지 않을 수 있으므로, 반드시 "--reset" 을 붙여주거나, 기존의 build 된 json 파일을 삭제해야 합니다.

 

이를 통해 기본적으로 truffle 을 사용한 기본 코드 작성에 대해서 알아보았습니다.

 

다음 포스팅에서는 본격적으로 조그마한 주제를 이용해서, truffle 을 이용해 간단한 기능을 구현해보도록 하겠습니다.

 

감사합니다.

반응형

댓글