코드코코

[솔리디티][디앱] lottery - Lottery Bet 테스트 본문

카테고리 없음

[솔리디티][디앱] lottery - Lottery Bet 테스트

코드코코 2022. 2. 14. 23:53

test/lottery.test.js

const Lottery = artifacts.require('Lottery');

//[deployer,user1,user2] : ganache-cli에서 생성한 주소가 차례대로 들어옴.
contract('Lottery', function ([deployer, user1, user2]) {

  let lottery;

  beforeEach(async () => {
    console.log('Before each');

    //테스트용 배포
    lottery = await Lottery.new()
  })

  // it('Basic test', async () => {
  //   console.log('Basic test');

  //   let owner = await lottery.owner();
  //   //let value = await lottery.getSomeValue();

  //   console.log(`owner :  ${owner}`);
  //   console.log(`value :  ${value}`);

  //   assert.equal(value, 5);
  // })

  //it.only : 특정 케이스만 테스틑 할 때
  it('getPot should return current pot', async () => {
    let pot = await lottery.getPot();
    // 처음에는 팟머니가 없는 상황이라 0
    assert.equal(pot, 0);
  })

  describe('Bet', function () {
    it.only('should fail when the bet money is not 0.005 ETH', async () => {
      // Fail transaction
      // 바이트 하나 보내고, 트랜잭션 오브젝트
      await lottery.bet('0xab', { from: user1, value: 4000000000000000 })
      // transaction object {chainId, value, to, from, gas(Limit), gasPrice}
      // chainId : 블록체인마다, 네트워크 마다 다른 체인아이디
      // value : 이더
      // to : address
      // from : 누가 보냇는지
    });

    it('should put the bet to the bet queue with 1 bet', async () => {
      // bet

      // check contract balance === 0.005

      // check bet info

      // check log
    })
  })
})

테스트하기 위해 가나슈로 테스트서버열기

ganache-cli -d -m tutorial

 

테스트 실행

truffle test test/lottery.test.js

조건에 미충족 시 에러 확인

조건 충족시 통과 확인

 

open zeppelin

솔리디티 기반의 스마트 컨트렉트를 개발하는 표준 프레임워크. truffle과 유사하지만, truffle 보다 기능이 다양함

 

open zeppelin의 test-helper 기능을 참고하여 에러 핸들링 테스트 코드 작성

test/assertRevert.js

module, exports = async (promise) => {
  try {
    //promise니까 await 걸기.
    await promise;
    // promise를 기다렸는데 에러가 catch문 쪽으로 넘어가지 않으면 문제가 있으므로,
    // 리버트가 예상되었으나 일어나지 않았다는 문구를 작성.
    assert.fail('Expected revert not received')
  } catch (error) {
    //원하는 대로 에러가 캐치문으로 온다면, 에러 메세지를 받아 search한다. search는 revert의 인덱스를 구할 수 있게 함.
    const revertFound = error.message.search('revert') >= 0;
    assert(revertFound, `Expected "revert", get ${error} instead`);
  }
}

test/lottery.test.js 파일 상단에 assertRevert.js 불러오기

테스크 코드가 올바르게 작동하는 지 확인

truffle test test/lottery.test.js

 

성공한 트랜잭션 테스트 코드 작성

contractAddress : 새로운 스마트 컨트랙트를 만드는 트랜잭션일 경우 값이 나옴.

args: logs의 이벤트의 파라미터들도 확인가능.

 

코드 추가 하여 테스트

 

상단에 betAmount 선언 및 할당 후, value : 5000000000000000를 value: betAmount로 변경

betInfo test 코드 작성

 

 

위에서 확인했던,

test/expectEvent.js 파일 생성 및 작성

npm i chai

 

test/lottery.test.js 에서 expectEvent 불러오기

테스트 코드 추가

WIN으로 바꿔서 테스트 해보면 실패가 뜨는 것을 확인 할 수 있음.

bet 부분만 전체 테스트 : only 키워드 사용

test/lottery.test.js 전체코드

const Lottery = artifacts.require('Lottery');
const assertRevert = require('./assertRevert')
const expectEvent = require('./expectEvent');

//[deployer,user1,user2] : ganache-cli에서 생성한 주소가 차례대로 들어옴.
contract('Lottery', function ([deployer, user1, user2]) {

  let lottery;
  let betAmount = 5 * 10 ** 15;
  let bet_block_interval = 3;

  beforeEach(async () => {
    //console.log('Before each');

    //테스트용 배포
    lottery = await Lottery.new()
  })

  // it('Basic test', async () => {
  //   console.log('Basic test');

  //   let owner = await lottery.owner();
  //   //let value = await lottery.getSomeValue();

  //   console.log(`owner :  ${owner}`);
  //   console.log(`value :  ${value}`);

  //   assert.equal(value, 5);
  // })

  //it.only : 특정 케이스만 테스틑 할 때
  it('getPot should return current pot', async () => {
    let pot = await lottery.getPot();
    // 처음에는 팟머니가 없는 상황이라 0
    assert.equal(pot, 0);
  })

  describe.only('Bet', function () {
    it('should fail when the bet money is not 0.005 ETH', async () => {
      // Fail transaction
      // assertRevert의 인자가 보내는 에러를 assertRevert가 받아서 try-catch 문으로 받는다.
      // 에러이므로 catch 문으로 들어온다. 에러문에 revert 단어가 있는지 없는지 확인하고 revert 단어가 있다면, 에러를 제대로 확인 하였다는 의미.
      await assertRevert(lottery.bet('0xab', { from: user1, value: 4000000000000000 })) // queue의 0번
      // transaction object {chainId, value, to, from, gas(Limit), gasPrice}
      // chainId : 블록체인마다, 네트워크 마다 다른 체인아이디
      // value : 이더
      // to : address
      // from : 누가 보냇는지


    });

    it('should put the bet to the bet queue with 1 bet', async () => {
      // bet
      // 바이트 하나 보내고, 트랜잭션 오브젝트
      let receipt = await lottery.bet('0xab', { from: user1, value: betAmount })
      //console.log(receipt);

      let pot = await lottery.getPot();
      assert.equal(pot, 0)

      // check contract balance === 0.005
      // 트러플에서 web3가 주입되어 있음.
      let contractBalance = await web3.eth.getBalance(lottery.address);
      assert.equal(contractBalance, betAmount);

      // check bet info
      let currentBlockNumber = await web3.eth.getBlockNumber();
      bet = await lottery.getBetInfo(0);

      assert.equal(bet.answerBlockNumber, currentBlockNumber + bet_block_interval);
      assert.equal(bet.bettor, user1);
      assert.equal(bet.challenges, '0xab');

      // check log
      await expectEvent.inLogs(receipt.logs, "BET");
    })
  })
})

contracts/Lottery.sol 전체코드

pragma solidity >=0.4.22 <0.9.0;

contract Lottery {
  struct BetInfo {
    uint256 answerBlockNumber;
    address payable bettor; //payable : 해당 주소로 트랜스퍼 하기 위해서 붙임.
    bytes challenges; // 0Xab..
  }

//tail이 증가하면서 값을 넣어줌
  uint256 private _tail;
  //값을 검증할 때는 _head 부터 차례대로 값을 가져와서 검증
  uint256 private _head;
  //자료구조 queue
  mapping(uint256 => BetInfo) private _bets;


  //주소를 오너로 설정
  //public : 자동으로 getter를 만들어 준다, 스마트 컨트랙트 외부에서 바로 오너의 값을 알 수 있다.
  address public owner;

  // 블록해시로 확인할 수 있는 제한 256으로 고정 
  uint256 constant internal BLOCK_LIMIT = 256;
  // +3 번째블럭으로 고정
  uint256 constant internal BET_BLOCK_INTERVAL = 3;
  // 배팅금액 0.005eth 고정
  uint256 constant internal BET_AMOUNT = 5 * 10 ** 15; 

  //팟머니 
  uint256 private _pot;

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

//스마트 컨트랙트가 생성될 때,, 배포가 될때, 가장 먼저 실행되는 함수.
  constructor() public {
    owner = msg.sender;
    // 배포가 될 때, 보낸사람을 오너로 저장하겠다.
    // msg.sender : 스마트 컨트랙트에서 사용하는 전역변수 
  }

  // 테스트용 코드 주석처리
  // function getSomeValue() public pure returns (uint256 value){
  //   return 5;
  // }

// view : 스마트 컨트랙트에 있는 변수를 조회할 때
  function getPot() public view returns (uint256 pot){
    return _pot;
  }

  
  // /**
  // * 베팅 함수
  // *
  // * @dev 베팅을 한다. 유저는 0.005 ETH를 보내야 하고, 베팅용 1 byte 글자를 보낸다.
  // * 큐에 저장된 베팅 정보는 이후 distribute 함수에서 해결된다.
  // * @param challenges 유저가 베팅하는 글자
  // * @return 함수가 잘 수행되었는지 확인하는 bool 값
  // *
  // */
  function bet(bytes memory challenges) public payable returns (bool result){
    // Check the proper ether is sent
    require(msg.value == BET_AMOUNT, "not enough ETH");
    // Push bet to the queue
    require(pushBet(challenges),"Fail to add a new Bet Info");
    // Emit event
    emit BET(_tail - 1, msg.sender, msg.value, challenges, block.number + BET_BLOCK_INTERVAL);

    return true;
  }
    // Save the bet to the queue

  // Distribute : 검증
    // check the answer
  
  //getter
  function getBetInfo(uint256 index) public view returns (uint256 answerBlockNumber, address bettor, bytes memory challenges){
    BetInfo memory b = _bets[index];
    answerBlockNumber = b.answerBlockNumber;
    bettor = b.bettor;
    challenges = b.challenges;
  }

  //queue 이용하니 puch와 pop의 개념이 필요
  function pushBet(bytes memory challenges) internal returns (bool) {
    BetInfo memory b;
    //베터는 보낸사람 , 버전업이 되어 전송하려면 payable 붙여줘야함.
    b.bettor = payable(msg.sender); // 20 bytes
    //block.number : 현재 트랜잭션에 들어가는 블럭의 수를 가져옴
    b.answerBlockNumber = block.number + BET_BLOCK_INTERVAL; // 32 bytes  20000 gas
    b.challenges = challenges; // byte  // 20000gas 가정
    
    _bets[_tail] = b;
    _tail++; // 32 bytes 값 변화 // 20000gas 가정 -> 5000 gas 

    return true;
  }

  function popBet(uint256 index) internal returns (bool){
    //매핑 이기때문에 리스트에서 삭제하기보다는 단순히 값을 초기화 하자.
    //맵에 있는 값을 delete하게 되면, gas를 돌려받게 된다. 이것의 의미는 더이상 데이터를 블록체인 데이터에 저장하지 않겠다의 의미. state database에 저장된 값을 그냥 없앤다
    delete _bets[index];
    return true;
  }
}