Home - wogus2421/BlockChain_practice GitHub Wiki

Dapp 관련한 간단한 예제(truffle 이용한 pet-shop)

  • 조건
    • 16마리의 애완동물에 대한 정보를 가지고 있다.
    • pet shop에서 입양되는 애완동물과 Ethereum 주소를 연관시키는 Dapp이 목표이다.
    • 기본 웹사이트의 구조, style을 제공한다(예제에 구현되어있다)
    • front-end 로직과 smart-contract 를 작성하면 완성되는 형식이다.

개발환경

  • 다음의 것들은 깔려있어야 한다.
    • Node.js LTS_ver, npm
    • Git
    • Truffle
    • Ganache

시작

  • tuffle을 이용하여 truffle 프로젝트를 생성한다

    • truffle unbox (box_name) 를 이용하여 생성한다. 이 경우 예제에 필요한 것들이 같이 생성된다.
    • truffle init 을 통하여 truffle 박스를 만든다(init을 이용할 경우 빈 프로젝트만 생성된다)
  • 만들어진 box의 구조는 다음과 같다.

    • contracts/ : smart contract를 위한 solidity파일을 저장하는 장소. Migrate.sol이라는 파일이 이미 존재한다.
    • Migrations/ : truffle에서 smart contract 배포를 처리하기 위해 이용하는 시스템. 변경사항을 추적하는 추가적 smart contract이다
    • test/ : smart contract의 테스트 코드 JS와 Solidity 모두 가능하다.
    • truffle.js : truffle 설정파일

smart contract 작성

  • 다음과 같은 Adoption.sol 파일을 추가하였다.
pragma solidity ^0.4.17;
contract Adoption {
  address[16] public adopters;
  function adopt(uint petId) public returns (unit) {
    require(petId >= 0 && petId <= 15);
    adopters[petId] = msg.sender;
    return petId;
  }
  function getAdopters() public view returns (address[16]){
    return adopters;
  }
}
  • public으로 선언한 변수는 자동으로 getter 함수를 갖는다. key 값을 통하여 단일 변수을 얻어올 수있다.
  • 함수에 대한 간략한 설명
    • 여기엔 없으나 최근의 버전에선 생성자를 만들 때 function Adoption() 으로도 가능하고 constucter() 로도 가능하다.
    • adopt : petId를 받아 분양하고, adopter의 petId 인덱스에 메세지를 보낸(계약한) 사람의 주소를 저장하고, 성공시 그 petId를 return 한다.
    • getAdopters : 매개변수는 없으며 adopters 라고하는 16개짜리 address 배열을 return한다.

Solidity는 컴파일 기반의 언어이고, EVM 에서 실행되기 위하여 바이트 코드로 컴파일 해야한다.

  • truffle compile 명령어를 이용하여 컴파일 하면 된다.
  • 컴파일 시, .\build/contracts 에 compile된 정보를 담은 json 파일이 생긴다
    • 코드를 고쳤을 시에는 json 파일을 지우고 다시 컴파일 하도록 하자.

migration

  • 컴파일에 성공하면 다음 단계로 migrate 해야한다

    • migration은 어플리케이션의 contract state를 변경하여 다음 state로 이동시키는 script다. 첫 번 째 migration은 새 코드를 deploy하는 것이지만, 시간이 지남에 따라 data를 이동하거나, contract를 새로운 것으로 대체할 수 있다.
  • migrations/ 폴더에 default로 1_initial_migration.js 파일이 존재한다.

    • 이 js 파일은 이후 smart contract의 magration을 관찰하기 위하여 Migration.sol의 deploy를 관리하며, 수정되지 않은 contract의 2중 배포를 막는다.
  • migrations/ 디렉토리에 2_deploy_contracts.js파일을 생성한다. 내용은 다음과 같다.

var Adoption = artifacts.require("./Adoption.sol");
moodule.exports = funtion(deployer) {
  deployer.deploy(Adoption);
}
  • 블록체인으로 contract를 migrate 하기 전에 블록체인을 실행해야한다. Ganach를 이용한 private net을 사용할 것이므로 Ganache를 키면된다.
  • truffle.js 에서 Ganache를 설정한다. 다음과 같은 형태로하면 된다.
module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // for more about customizing your Truffle configuration!
  networks: {
    development: {
      host: "127.0.0.1",
      port: 7545,
      network_id: "*" // Match any network id
    }
  }
};
  • 터미널에서 truffle migrate 명령어를 이용하여 migration 한다.
  • 이후 Ganache를 통하여 블록체인이 transection을 통하여 블록을 생성하였는지 확인한다. 이 migration을 통하여 100이더였던 것이 조금 깎인다.

smart contract test

  • test는 Solidity 와 JavaScript 모두 가능하다.
  • test/ 디렉토리에 TestAdoption.sol 파일을 생성한다.
  • 내용은 다음과 같다.
pragma solidity ^0.4.17;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";

contract TestAdoption{
  Adoption adoption = Adoption(DeployedAddresses.Adoption());

  function testUserCanAdopPet() public{
    unit returnedId = adoption.adopt(8);
    unit expected = 8;
    Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
  }

  function testGetAdopterAddressByPetId() public {
    address expected = this;
    address adopter = adoption.adopters(8);
    Assert.equal(adopter, expected, "Owner of pet ID 8 should be recoreded.");
  }

  function testGetAdopterAddressByPetIdInArray() public {
    address expected =this;
    address [16] memory adopters = adoption.getAdopters();
    Assertequal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
  }
}
  • imports

    • Assert.sol : 테스트에 사용할 assertion을 제공, global이다.
    • DeployedAddresses.sol : 테스트 실행시에 테스트할 contract를 새로 배포한다. 이때 배포한 contract의 address를 가져오는 역할. 이또한 전역 truffle 파일이다.
    • Adoption.sol : 우리가 테스트하고자 하는 contract
  • functions

    • testUserCanAdoptPet : petId 8 을 이용하여 adopt()를 실행하고, 예측값인 8을 따로선언하여 Assert로 확인한다.
    • testGetAdopterAddressByPetId : Adoption.sol 에서 public 변수 선언으로 인한 getter를 확인하는 함수이다.
    • testGetAdopterAddressByPetIdInArray : adopters 배열에 PetId 8 을 첫 입양한 테스트를 알기에 그 contract 주소와 비교한다.
      • solidity에서 memory 는 contract의 storage에 저장하는 것이 아닌 일시적인 값 저장을 의미한다.
  • 작성이 끝났다면, truffle test 명령어를 이용하여 test를 진행한다.

front-end 만들기

  • truffle unbox 를 통하여 만든 디렉토리이므로 이미 pet shop 예제 진행을 위한 front-end 코드가 포함되어있다.
  • 몇가지 설정만 바꿔주면 바로 사용이 가능하다.
  1. app.js
  • src/js/app.js 파일을 연다
  • initWeb3 함수의 코멘트에 다음과 같은 것을 채워 넣는다.
if(typeof web3 !== 'undefined') {
  App.wep3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
}
web3 = new Web3(App.web3provider);
  • 이미 활성화 된 web3 가 있는지 확인하고, 있으면 그것을 사용하고 아니면 생성하여 사용한다.
  • initContract 함수의 코멘트가 있는 부분에 다음과 같이 채워 넣는다.
$.getJSON('Adoption.json', function(data) {
  // Get necessary contract artifact file and instantiate it with truffle contract
  var AdoptionArtifact = data;
  App.contracts.Adoption = TruffleContract(AdoptionArtifact);

  // Set the provider for our contract
  App.contracts.Adoption.setProvider(App.web3Provider);

  // Use our contract to retrieve and mark the adopted pets
  return App.markAdopted();
});

  • 코드에 대한 설명은 다음과 같다.

    • 먼저 smart contract 의 artifact를 검색한다. artifact는 deploy된 address 및 ABI와 같은 contract에 대한 정보이다.
    • callback에 artifact 가 생기면 TruffleContract로 전달하여 contract 의 instance를 생성한다.
    • instance화 된 contract를 통하여 web3 provider를 설정한다.
    • 이전에 이미 분양된 pet 인경우엔 markAdopted를 이용하여 UI 업데이트
  • markAdopted 함수에 주석을 제거하고 다음과 같이 채워 넣는다.

var adoptionInstance;
App.contracts.Adoption.deployed().then(function(instance) {
  adoptionInstance = instance;
  return adoptioinInstance.getAdopters.call();
}).then(function(adopters) {
  for (i = 0; i< adopters.length; i++) {
    if(adopters[i] !== '0x0~') { // 0~ -> 0 40개
      $('.panel-pet').eq(i).find('button').text('Success').attr('disabled',true);
    }
  }
}).catch(function(err) {
  console.log(err.message);
});
  • 코드에 대한 설명은 다음과 같다.

    • deploy된 Adoptioin contract에 진입하여 그 instance에 getAdopters()를 호출한다.
    • call() 을 사용하면 transection 없이 데이터를 읽을 수 있다 즉, 이더를 소비하지 않는다.
    • getAdopters() 를 이용하여 모든 항목을 확인한다. 이더리움은 0으로 초기화 되어있기에 0이 아니면 address가 저장되어 있는것이다.
    • 주소가 존재한다면, adopt 버튼을 비활성화 하고 text를 success로 바꾸어 준다
    • 모든 error는 console에 로깅된다.
  • 마지막으로 handleAdopt 함수의 주석을 지우고 다음과 같이 채워넣는다

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;
    //Execute adopt as a transection by sending account
    return adoptionInstance.adopt(petId, {from: account});
  }).then(function(result) {
    return App.markAdopted();
  }).catch(function(error) {
    console.log(err.message);
  });
});
  • 코드에 대한 설명은 다음과 같다
    • web3를 이용하여 사용자의 계정을 가져온다. error check 후 callback 에서 첫 번 째 계정을 선택한다.
    • deploy된 constract를 가져와서 adoptionInstance에 저장. transection을 보낼 것 이기 때문에 from 의 계정이 필요하며 이더로 지불하는 gas 가필요하다. adopt 함수를 실행하여 transection을 보낸다.
    • transection 전송의 결과는 transection object 이다. 오류가 발생하지 않았다면 markAdopted로 UI를 동기화한다.

dapp 사용

  1. 메타마스크 사용
  • 메타마스크를 로그인한다
  • main network에 접속한 상태에서 커스텀을 누르고, ganache의 주소(private net)으로 접속한다.
  • ganache의 계좌를 import하여 동기화가 잘 되었는지 확인한다.
  1. lite-server 설치 및 사용
  • npm install lite-server -g 로 다운받는다
  • bs-config.jason 파일을 열어서 다음과 같은 내용을 확인한다.
{
  "server":{
    "baseDir": ["./src","./build/contracts"]
  }
}
  • pakage.jason 파일도 확인한다.
"scripts": {
  "dev": "lite-server",
  "test": "echo \"Error: no test specified\" && exit 1"
},
  • 모두 확인하였다면 npm run dev로 실행 시킬 수있다.

오류

  • 메타마스크 이슈로 인한 실패 가능성이 있다
  • 가나슈랑 메타마스크 연동할 때 에러가 잦다............