// 에드센스

 

므ㅏ

강의출처:


1. 사용할 컨트랙에 대한 정보를 가져온다.

contract("Lottery", ([deployer, user1, user2]) => {
  ...
});

트러플에서는 mocha와 chai를 스마트컨트랙에서 사용할 수 있도록 조절해 놓았다고 한다.

따라서 테스트를 시작할 때 contract 키워드로 시작하면 된다.

 

첫번째 인자로는 컨트랙트 이름이 들어가고,

두번째 인자로는 콜백함수가 들어간다. 이 콜백함수의 파라미터로 ganache-cli에서 생성되는 10개의 account가 들어간다.

즉 최대 10개의 인자를 넣을 수 있다.

 

위 코드처럼 3개를 썼다는 것은 

deployer에 0xd53....

user1에 0xd07....

user2에 0x040....

이렇게 3개의 주소가 파라미터로 들어간 것이다.

 

 

 

 


2. 스마트 컨트랙 배포

테스트를 하려면 스마트컨트랙 코드가 블록에 들어가서 실행되어야 하므로 컨트랙을 배포해주자

contract("Lottery", ([deployer, user1, user2]) => {
  beforeEach(async () => {
    console.log("Before each");
    lottery = await Lottery.new(); //테스트에서 사용할 스마트 컨트랙 배포
  });
});

 

 

 

 


3. 테스트코드 작성

  let betAmount = 5 * 10 ** 15;
  let BET_BLOCK_INTERVAL = 3;
  
  describe("Bet", async () => {
    it("베팅큐에 값이 잘 들어갔는지 확인하기", async () => {
      // 베팅한다
      const receipt = await lottery.bet("0xab", {
        from: user1,
        value: betAmount, // 5 * 10 ** 15 -> 0.005ETH
      });

      // pot머니(상금)은 아직 결과가 들어나지 않았기에 0이어야 한다.
      let pot = await lottery.getPot();
      assert.equal(pot, 0);

      // 컨트랙트 주소로 0.005이더가 들어왔는지 확인한다
      let contractBalance = await web3.eth.getBalance(lottery.address); // web3가 자동으로 주입돼있다
      assert.equal(contractBalance, betAmount);

      // 베팅인포가 제대로 들어갔는지 확인한다
      let currentBlockNumber = await web3.eth.getBlockNumber();
      let bet = await lottery.getBetInfo(0); // 큐 제일 앞의 베팅정보를 가져온다

      // 큐에 넣은 베팅정보가 올바른지 확인
      assert.equal(
        bet.answerBlockNumber,
        currentBlockNumber + BET_BLOCK_INTERVAL
      );
      assert.equal(bet.bettor, user1);
      assert.equal(bet.challenges, "0xab");

      // 로그(BET이라는 이벤트)가 제대로 찍혔는지 확인한다
      await expectEvent.inLogs(receipt.logs, "BET");
    });

    it("0.005이더가 안들어왔을때는 실패해야한다", async () => {
      // 트랜잭션 실패
      await assertRevert(
        // 두번째 인자는 트랜잭션 오브젝트, transaction object란
        // (chainId, value, to, from, gas(limit), gasPrice)
        lottery.bet("0xab", { from: user1, value: 4000000000000000 }) // bet함수 실행 -> 0.004ETH가 들어갔기에 트잭이 실패나야한다
      );
    });
  });

 

 

event, emit을 테스트하기

const assert = require("chai").assert;

// 직접 발생시킨 event의 로그가 있는지 확인한다.
const inLogs = async (logs, eventName) => {
  const event = logs.find((e) => e.event === eventName);
  assert.exists(event);
};

module.exports = {
  inLogs,
};

[Lottery Dapp 개발하기] 2.4까지의 분량이다. 

밀린 블로그 글써내기중이라 그간 했던 분량을 한번에 쑤셔넣는중.

이 글을 읽는 여러분들에게 도움이 되면 좋겠지만 딱히 그럴려고 쓰는 글은 아니고 혼자서 끄적이는 글.

따라서 생략된 설명이나 이걸 왜 설명? 하는 부분이 있을것이다.

 

 

가보자~

강의 출처:


1. 코딩할 것들

  • 스마트 컨트랙
    • 베팅정보를 담는 구조체
    • 베팅정보를 저장할 큐 자료구조
    • 쌓인상금_가져오기()
    • 베팅큐에서_베팅정보_가져오기()
    • 베팅하기()
      • 베팅을_베팅큐로_밀어넣기()
      • 베팅을_베팅큐에서_빼기()
  • 테스트코드 (다음글에서)
    • beforeEach
    • 베팅큐에 베팅정보가 잘 들어간경우 테스트
    • 베팅큐에 베팅정보가 못들어간경우 테스트

 

 

 


2. Lottery.sol에 코딩한 것들

베팅 정보를 담는 구조체

  struct BetInfo {
    uint256 answerBlockNumber; // 맞출 블록의 넘버
    address payable bettor; // 돈을 건 사람 주소, 특정주소에 돈을 보내려면 payable을 써줘야함
    bytes challenges; // 문제. ex) 0xab
  }

 

 

 

BetInfo 구조체를 넣어줄 큐를 구현

  // 매핑으로 큐를 구현하기 위한 변수
  uint256 private _tail;
  uint256 private _head;

  // 키는 uint, 값은 BerInfo인 매핑
  mapping(uint256 => BetInfo) private _bets;

index - value의 쌍을 mapping을 통해 구현한다.

 

 

 

상금 액수 조회하기

  uint256 private _pot;
  
  // 스마트 컨트랙의 변수를 가져와서 쓰려면 view 키워드를 쓴다
  // 더 자세히는, Storage 자료를 읽어서 보여줄때 사용. 수정할때는 사용x
  // Storage 자료는 블록에 영구적으로 기록된 값(하드디스크)
  // 반대인 Memory 자료는 임시로 저장되는 값(RAM)
  function getPot() public view returns (uint256 value) {
    return _pot;
  }

 

 

 

베팅하기

  // 이벤트 객체
  event BET(uint256 index, address bettor, uint256 amount, bytes1 challenges, uint256 answerBlockNumber);

  function bet(bytes challenges) public payable returns (bool result) {
    // 베팅할 금액(0.005ETH)이 제대로 왔는지 확인
    // require는 if역할, msg.value는 컨트랙트가 받은금액, 'Not Enough ETH'은 조건이 false때 출력할 문구
    require(msg.value == BET_AMOUNT, 'Not Enough ETH');

    // 큐에 베팅정보를 넣기
    require(pushBet(challenges), 'Fail to add a new Bew Info');

    // 이벤트 객체를 체인에 기록한다
    // (몇번째 배팅인지, 누가 배팅했는지, 얼마 배팅했는지, 어떤글자로 베팅했는지, 어떤 블록에 정답이 있는지)
    emit BET(_tail - 1, msg.sender, msg.value, challenges, block.number + BET_BLOCK_INTERVAL);
    return true;
  }

event와 emit

트랜잭션이 완료되면 트랜잭션 영수증을 발행한다.

영수증에는 트랜잭션 실행 동안 발생했던 모든 행동로그가 기록되어 있다.

event는 이런 로그를 만들기 위한 객체

emit은 만들어진 event 객체를 체인에 올리기.

더보기

 

 

 

 

베팅정보들을 담은 큐에서 베팅정보 1개 꺼내오기

  function getBetInfo(uint256 index)
    public
    view
    returns (
      uint256 answerBlockNumber, // 반환값1
      address bettor, // 반환값2
      bytes challenges // 반환값3
    )
  {
    BetInfo memory b = _bets[index]; // memory형 변수는 함수가 끝나면 지워짐, storage형 변수는 블록에 영영 기록됨
    answerBlockNumber = b.answerBlockNumber; // 반환값1
    bettor = b.bettor; // 반환값2
    challenges = b.challenges; // 반환값3
  }

 

 

 

베팅큐에 베팅정보 push

  function pushBet(bytes challenges) internal returns (bool) {
    BetInfo memory b; // 베팅정보를 하나 생성하고 세팅한다
    b.bettor = msg.sender; // 함수 호출한 사람
    b.answerBlockNumber = block.number + BET_BLOCK_INTERVAL; // block.number는 현재 이 트랜잭션이 들어가게되는 블록넘버를 가져온다
    b.challenges = challenges; // 내가 베팅한 값

    _bets[_tail] = b; // 큐에 넣고
    _tail++; // 테일 포인터 조정

    return true;
  }

 

 

 

베팅큐에서 베팅정보 1개 pop

  function popBet(uint256 index) internal returns (bool) {
    // 스마트컨트랙에서 덧셈이든, 뺄셈이든 하면 가스를 소모한다.
    // delete를 하면 가스를 돌려받는다. 왜? 이더리움 블록체인에 저장하고 있는 데이터를 더 이상 저장하지 않겠다는 것이기에
    // 즉, 상태 데이터베이스에 있는 값을 그냥 가져오겠다는 것이기에
    // 그러니 필요하지 않은 값이 있다면 delete를 해주자
    delete _bets[index];
    return true;
  }

 

 

 

 


3. 전체 코드

pragma solidity >=0.4.21 <0.6.0;

contract Lottery {
  struct BetInfo {
    uint256 answerBlockNumber; // 맞출 블록의 넘버
    address payable bettor; // 돈을 건 사람 주소, 특정주소에 돈을 보내려면 payable을 써줘야함
    bytes challenges; // 문제. ex) 0xab
  }

  // 매핑으로 큐를 구현하기 위한 변수
  uint256 private _tail;
  uint256 private _head;

  // 키는 uint, 값은 BerInfo인 매핑
  mapping(uint256 => BetInfo) private _bets;

  address public owner;

  uint256 internal constant BLOCK_LIMIT = 256; // 블록해시를 확인할 수 있는 제한
  uint256 internal constant BET_BLOCK_INTERVAL = 3; // 2번 블록에서 베팅을 하면 5번 블록에서 결과가 나온다
  uint256 internal constant BET_AMOUNT = 5 * 10**15; // 0.005ETH

  uint256 private _pot;

  // 이벤트 로그들을 한번에 모을 수 있다, BET이라는 로그들을 찍어줘
  // (몇번째 배팅인지, 누가 배팅했는지, 얼마 배팅했는지, 어떤글자로 베팅했는지, 어떤 블록에 정답이 있는지)
  event BET(uint256 index, address bettor, uint256 amount, bytes1 challenges, uint256 answerBlockNumber);

  constructor() public {
    owner = msg.sender; // msg.sender는 전역변수
  }

  function getPot() public view returns (uint256 value) {
    // 스마트 컨트랙의 변수를 가져와서 쓰려면 view 키ㅕ드를 쓴다
    return _pot;
  }

  // Bet (베팅하기)
  /*
    @dev 베팅을 한다. 유저는 0.005ETH를 보내야하고 베팅용 1byte 글자를 보낸다, 큐에 저장된 베팅 정보는 이후 distribute 함수에서 해결한다
    @param challenges 유저가 베팅하는 글자
    @return 함수가 잘 수행되었는지 확인하는 bool 값
    */
  function bet(bytes challenges) public payable returns (bool result) {
    // 돈이 제대로 왔는지 확인
    // require는 if역할, msg.value는 컨트랙트가 받은금액, 문자열은 조건이 false때 출력할 문구
    require(msg.value == BET_AMOUNT, 'Not Enough ETH');

    // 큐에 베팅정보를 넣기
    require(pushBet(challenges), 'Fail to add a new Bew Info');

    // 이벤트 로그를 찍는다
    // (몇번째 배팅인지, 누가 배팅했는지, 얼마 배팅했는지, 어떤글자로 베팅했는지, 어떤 블록에 정답이 있는지)
    emit BET(_tail - 1, msg.sender, msg.value, challenges, block.number + BET_BLOCK_INTERVAL);
    return true;
  }

  // 베팅한 값을 큐에 저장함

  // Distribute (검증하기)
  // 베팅한 값을 토대로 결과값을 검증
  // 검증결과가 틀리면 팟머니에 돈을 넣고, 맞으면 돈을 유저에게 준다

  // 베팅정보들을 담고있는 큐에서 베팅정보 가져오기
  function getBetInfo(uint256 index)
    public
    view
    returns (
      uint256 answerBlockNumber,
      address bettor,
      bytes challenges
    )
  {
    BetInfo memory b = _bets[index]; // memory형 변수는 함수가 끝나면 지워짐, storage형 변수는 블록에 영영 기록됨
    answerBlockNumber = b.answerBlockNumber; // 반환값1
    bettor = b.bettor; // 반환값2
    challenges = b.challenges; // 반환값3
  }

  // 큐 push
  function pushBet(bytes challenges) internal returns (bool) {
    BetInfo memory b; // 베팅정보를 하나 생성하고 세팅한다
    b.bettor = msg.sender; // 함수 호출한 사람
    b.answerBlockNumber = block.number + BET_BLOCK_INTERVAL; // block.number는 현재 이 트랜잭션이 들어가게되는 블록넘버를 가져온다
    b.challenges = challenges; // 내가 베팅한 값

    _bets[_tail] = b; // 큐에 넣고
    _tail++; // 테일 포인터 조정

    return true;
  }

  // 큐 pop
  function popBet(uint256 index) internal returns (bool) {
    // delete를 하면 가스를 돌려받는다. 왜? 상태데이터베이스에 저장된 값을 그냥 뽑아오겠다는 것이기에
    // 그러니 필요하지 않은 값이 있다면 delete를 해주자
    delete _bets[index];
    return true;
  }
}

+ Recent posts