Truffle를 활용한 Pet-Shop 튜터리얼 분석

광고 클릭시 많은 힘이 됩니다. 


Truffle 튜터리얼 중 펫샵을 활용해서 프론트 까지 적용해서 어떤식으로 웹에서 연동되는지 살펴보자.

아래 주소에 있는 내용은 깔끔하게 정리되어 있다.  따라해보는 걸 추천한다. 

여기 글은 펫샵에 사용되는 소스를 분석해보자 한다. 

이더리움 플랫폼 위 DAPP개발 프레임워크 중 하나인 truffle사용시 개발시 순서는 다음과 같다.

  • 개발환경을 설정한다.
  • truffle box( 보일러 플레이트처럼 만들어준다.)를 이용해서 pet-shop 을 해제
  • 스마트 계약 소스를 (.sol) 작성한다.
  • 컴파일 및 배포를 한다.
  • Ganeche 를 통해 테스팅을 한다.
  • 웹 브라우저에서 스마트 계약이 정상적으로 인터랙션이 제대로 이루어지는지 확인해본다.

아래 이미지는 결과 화면이다. Adopt 버튼을 통해서 success 이 제대로 이루어 지는 지 보면 된다.

MetaMask 를 통해서 창이 열리면서 Submit를 클릭해준다.

최종적으로 채택이 된 것을 웹에서 확인해볼 수 있다. ( 새로고침을 해야한다. )

결과물은 따라하다보면 쉽게 볼 수 있을것이다. 여기서 특이한 점은

MetaMask를 통한 Custom RPC 로 ganache private network 로 접속해서 진행

스마트 계약을 테스팅시 솔리디티 언어로 테스팅 하는 방법

우선 Adoption.sol 파일을 훝어보자.

pragma solidity ^0.4.17;

contract Adoption {
    address[16] public adopters;

    function adopt(uint petId) public returns (uint) {
        require(petId >= 0 && petId <= 15);

        adopters[petId] = msg.sender;

        return petId;
    }

    function getAdopters() public view returns (address[16]) {
        return adopters;
    }
}

주로 스마트 계약을 테스팅시 자바스크립트(mocha)로 계약 함수를 신뢰성을 테스트한다. 하지만 여기에선 바로 솔리디티 언어로 테스팅하는 걸 보여준다.

pragma solidity ^0.4.17;

//다양한 테스팅 도구 제공
import "truffle/Assert.sol";
//이미 서버에 배포된 계약 주소를 가져온다. 
import "truffle/DeployedAddresses.sol";
//계약 소스를 가져온다. 
import "../contracts/Adoption.sol";

contract TestAdoption {
    //Adoption 초기화
    Adoption adoption = Adoption(DeployedAddresses.Adoption());

    //Pet에 Adopt를 할수 있는지 테스트
    function testUserCanAdoptPet() public {
        //8번 아이디를 adopt 함수 실행
        uint returnedId = adoption.adopt(8);

        uint expected = 8;


        //값이 제대로 가져오는 지 체크
        Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recoreded");
    }

    //아이디를 통해 Adopter 주소를 가져오는 걸 테스팅
    function testGetAdopterAddressByPetId() public {
        //트랙잭션을 할 예정이므로 예상 값을 this로 설정 가능하다. (펫 구매한 사람 주소)
        address expected = this;

        //adopters 변수 설정되면 public getter 가 되므로 거기 위치에 8의 값을 가져와서 테스팅해본다. 
        address adopter = adoption.adopters(8);

        //값 맞게 들어오는지 체크
        Assert.equal(adopter, expected, "Owner of pet Id 8 should be recored");
    }

    function testGetAdopterAddressByPetIdInArray() public {
        //트랙잭션을 할 예정이므로 예상 값을 this로 설정 가능하다. (펫 구매한 사람 주소)
        address expected = this;

        //storeage, memory 를 설정해서 실제 물리적으로 저장 할 것인지 내부에서 저장할 건지 정한다. 
        //adoption 클래스에서 adopter 배열목록을 가져온다. 
        address[16] memory adopters = adoption.getAdopters();

        //가져온 adopter의 8번째가 맞는지 체크
        Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
    }
}

그리고 다음과 같이 테스팅을 해본다.

> truffle test
or
devlope 모드일 경우 단지 test 만 입력
Using network 'development'.

   Compiling ./contracts/Adoption.sol...
   Compiling ./test/TestAdoption.sol...
   Compiling truffle/Assert.sol...
   Compiling truffle/DeployedAddresses.sol...

     TestAdoption
       ✓ testUserCanAdoptPet (91ms)
       ✓ testGetAdopterAddressByPetId (70ms)
       ✓ testGetAdopterAddressByPetIdInArray (89ms)


     3 passing (670


그리고 웹쪽 프론트에서 이 계약 함수들을 사용하는 내용이다. App.js 에서 볼 수 있다.

주석을 달아봄으로써 해당 기능을 알아볼 예정이다. 

App = {
  web3Provider: null,
  contracts: {},

  //초기화
  init: function() {
    //펫 배열을 가져와서 리스트를 만든다. 
    $.getJSON("../pets.json", function(data) {
      var petsRow = $("#petsRow");
      var petTemplate = $("#petTemplate");

      for (i = 0; i < data.length; i++) {
        petTemplate.find(".panel-title").text(data[i].name);
        petTemplate.find("img").attr("src", data[i].picture);
        petTemplate.find(".pet-breed").text(data[i].breed);
        petTemplate.find(".pet-age").text(data[i].age);
        petTemplate.find(".pet-location").text(data[i].location);
        petTemplate.find(".btn-adopt").attr("data-id", data[i].id);

        petsRow.append(petTemplate.html());
      }
    });
    
    //지갑설정 함수를 실행한다. 
    return App.initWeb3();
  },

  
  initWeb3: function() {
    // Is there an injected web3 instance?
    if (typeof web3 !== "undefined") {
      //Metamask 가 실행시 현 지갑을 리턴한다. 
      App.web3Provider = web3.currentProvider;
    } else {
      //만약 지정된 지갑이 없는 경우 미리 설정된 Ganeche 지갑을 리턴한다. 
      App.web3Provider = new Web3.providers.HttpProvider(
        "http://localhost:7545"
      );
    }
    web3 = new Web3(App.web3Provider);

    return App.initContract();
  },
  
  //계약 초기화
  initContract: function() {
    /*
    Adoption.JSON 형태 
    {
    "contractName": "Adoption",
    "abi": [
      {
        "constant": true,
        "inputs": [
          {
            "name": "",
            "type": "uint256"
          }
        ],
        "name": "adopters",
       ...............
    */
    //Adoption 은 컴파일 시 나온 ABI JSON이다 여기에 기본 함수들이 표시가 된다. 
    //웹에서는 이 ABI JSON을 보고 실행을 할 수 있다.
    $.getJSON("Adoption.json", function(data) {
      // Get the necessary contract artifact file and instantiate it with truffle-contract
      var AdoptionArtifact = data;
      //미리 제공된 truffleContract 를 통해 Adoption 인스턴스 생성
      App.contracts.Adoption = TruffleContract(AdoptionArtifact);
      
      //지갑 설정
      App.contracts.Adoption.setProvider(App.web3Provider);

      //
      // 이미 채택된 애완 동물이 있는 경우 함수를 호출해서 데이터 확인 후 업데이트 할수 있도록 한다. 
      return App.markAdopted();
    });

    return App.bindEvents();
  },

  //이벤트 바인딩
  bindEvents: function() {
    $(document).on("click", ".btn-adopt", App.handleAdopt);
  },

  //채택된 아이템이 있는 경우 버튼을 success 로 변경
  markAdopted: function(adopters, account) {
    var adoptionInstance;

    App.contracts.Adoption.deployed()
      .then(function(instance) {
        adoptionInstance = instance;
        
        //전체 adopte 배열 주소를 리턴한다. 
        return adoptionInstance.getAdopters.call();
      })
      .then(function(adopters) {
        //for 문 돌면서 adopte 만큼 버튼을 success 로 바꾸고 비활성화를 시킨다. 
        for (i = 0; i < adopters.length; i++) {
          if (adopters[i] !== "0x0000000000000000000000000000000000000000") {
            $(".panel-pet")
              .eq(i)
              .find("button")
              .text("Success")
              .attr("disabled", true);
          }
        }
      })
      .catch(function(err) {
        console.log(err.message);
      });
  },
  //Adopt 버튼 클릭시 adopt 함수 실행하는 함수이다. (트랙잭션이 발생된다.)
  handleAdopt: function(event) {
    //기본 이벤트 블럭함
    event.preventDefault();

    //펫 아이디를 id 를 통해 쿼리해옴
    var petId = parseInt($(event.target).data("id"));

    var adoptionInstance;
    
    //지갑상에 주소를 가져온다. 
    web3.eth.getAccounts(function(error, accounts) {
      if (error) {
        console.log(error);
      }

      //처음 주소를 가져온다. 
      var account = accounts[0];

      
      App.contracts.Adoption.deployed()
        .then(function(instance) {
          adoptionInstance = instance;
          
          
          //petId, account를 넣어서 adopt 함수를 실행한다. 
          return adoptionInstance.adopt(petId, { from: account });
        })
        .then(function(result) {
        //완료 된 후 success 버튼으로 변경...잘 안됨..
          return App.markAdopted();
        })
        .catch(function(err) {
          console.log(err.message);
        });
    });
  }
};

$(function() {
  $(window).load(function() {
  //초기화
    App.init();
  });
});

Truffle을 이용해서 손쉽게 테스팅 가능하며 배포도 쉽게 가능한 걸 볼수 있다. 잘 배워놓으면 도움이 많이 된다고 본다. 


+ Recent posts