Lottery DApp 개발 - Lottery contract 개발[5]

Distribute 함수설계 & 테스트

Distribute 함수설계

수수료를 계산해 특정 주소와 owner에게 전하는 함수이다. 여기서는 간단하게 수수료를 0으로 지정하였다.

function transferAfterPayingFee(address payable addr, uint256 amount) internal returns(uint256) {
        uint256 fee = 0;
        uint256 amountWithoutFee = amount - fee;

        //transfer to addr
        addr.transfer(amountWithoutFee);
        //transfer to owner
        owner.transfer(fee);

        return amountWithoutFee;
    }

정답을 가져오는 함수를 만들어준다. mode가 트루일 경우에는 블록해쉬값을 리턴할 것이고 false일 경우 우리가 커스텀한 answer를 리턴한다.

bool private mode = false; //false = use answer for test, true = use real block hash

bytes32 public answerForTest;

만들어 준 후

function getAnswerBlockHash(uint256 answerBlockNumber) internal view returns(bytes32 answer){
        return mode ? blockhash(answerBlockNumber) : answerForTest;
    }

정답을 지정하는 settor도 만들어 준다.

function setAnswerForTest(bytes32 answer) public returns (bool result){
        require(msg.sender == owner, "only owner can set the answer for test mode");
        answerForTest = answer;
        return true;
    }


     /**
     * @dev 베팅결과값을 확인하고 팟머니를 분배한다.
     * 정답 실패 : 팟머니 축적, 정답 맞춤 : 팟머니 획득, 한글자 맞춤 또는 정답확인불가 : 베팅금액만 획득
     */
    function distribute() public {
        // head 부터 tail 까지 도는 loop
        uint256 cur;
        uint256 transferAmount;
        BetInfo memory b;
        BlockStatus currentBlockStatus;
        BettingResult currentBettingResult; //결과값

        for(cur = _head; cur < _tail; cur++){
            b = _bets[cur];
            currentBlockStatus = getBlockStatus(b.answerBlockNumber);

            if(currentBlockStatus == BlockStatus.Checkable){
                bytes32 answerBlockHash = getAnswerBlockHash(b.answerBlockNumber);
                //blockhash 랜덤 값으로 나와서 테스트하기 좋지 않음
                currentBettingResult = isMatch(b.challenges, getAnswerBlockHash(b.answerBlockNumber));
                //win bettor gets pot
                if(currentBettingResult == BettingResult.win){
                    //transfer pot
                    transferAmount = transferAfterPayingFee(b.bettor, _pot + BET_AMOUNT);
                    //pot = 0; 으로 초기화
                    _pot = 0;
                    //emit win
                    emit WIN(cur, b.bettor, transferAmount, b.challenges, answerBlockHash[0], b.answerBlockNumber);
                }
                //fail bettor's momey goes pot
                if(currentBettingResult == BettingResult.fail){
                    //pot = pot+bet_amount
                    _pot = _pot + BET_AMOUNT;
                    //emit fail
                    emit FAIL(cur, b.bettor, 0, b.challenges, answerBlockHash[0], b.answerBlockNumber);
                }
                //하나만 맞췄을 경우 refund bettor's money
                if(currentBettingResult == BettingResult.draw){
                    // transfer only bet_amount
                    transferAmount = transferAfterPayingFee(b.bettor, BET_AMOUNT);
                    //emit draw
                    emit DRAW(cur, b.bettor, transferAmount, b.challenges, answerBlockHash[0], b.answerBlockNumber);
                }
            }
            if(currentBlockStatus == BlockStatus.NotRevealed){
                break;
            }
            if(currentBlockStatus == BlockStatus.BlockLimitPassed){
                transferAmount = transferAfterPayingFee(b.bettor, BET_AMOUNT);
                emit REFUND(cur, b.bettor, transferAmount, b.challenges, b.answerBlockNumber);
            }
            popBet(cur);
        }
        _head = cur;
    }

emit의 event들을 작성해준다.

    event WIN(uint256 index, address bettor, uint256 amount, byte challenges, byte answer, uint256 answerBlockNumber);
    event FAIL(uint256 index, address bettor, uint256 amount, byte challenges, byte answer, uint256 answerBlockNumber);
    event DRAW(uint256 index, address bettor, uint256 amount, byte challenges, byte answer, uint256 answerBlockNumber);
    event REFUND(uint256 index, address bettor, uint256 amount, byte challenges, uint256 answerBlockNumber);

마지막으로 이 함수를 작성해준다.

    // 로터리에서는 운영자가 아닌 사용자가 계속 답을 확인하면서 베팅을 하는
    // 로터리를 구현할 것이기 때문에  함수를 사용할거임
    function betAndDistribute(byte challenges) public payable returns(bool result){
        bet(challenges);
        distribute();

        return true;
    }



Distribute 함수 테스트

###두 글자 다 맞춰서 이겼을 경우

        describe('when the answer is checkable', function() {
            it('should give the user the pot when the answer matches', async () => {
                //두 글자 다 일치했을 때
                await lotery.setAnswerForTest('0xab66ec4675534645e7f83e0c9d369356db51a012a0746e2f0e82df38a345b38b', {from:deployer});
                
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //1 -> 4
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //2 -> 5
                await lotery.betAndDistribute('0xab',{from:user1, value:betAmount}); //3 -> 6 //win
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //4 -> 7
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //5 -> 8
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //6 -> 9

                let potBefore = await lotery.getPot(); // ==0.01eth
                let user1BalanceBefore = await web3.eth.getBalance(user1);

                let receipt7 = await lotery.betAndDistribute('0xef',{form:user2,value:betAmount}); //7 -> 10 //여기에서 win확인 가능 user1에게 pot이 간다
                let potAfter = await lotery.getPot();// win : ==0 eth
                let user1BalanceAfter = await web3.eth.getBalance(user1); //==balancebefore + 0.015 eth
               
                //console.log(user1BalanceBefore.toString(), new web3.utils.BN('10000000000000000').toString());
                //pot 확인
                assert.equal(potBefore.toString(), new web3.utils.BN('10000000000000000').toString());
                assert.equal(potAfter.toString(), new web3.utils.BN('0').toString());
                
                user1BalanceBefore = new web3.utils.BN(user1BalanceBefore);
                assert.equal(user1BalanceBefore.add(potBefore).add(betAmountBN).toString(), new web3.utils.BN(user1BalanceAfter).toString());
             
            })

3번째에서 정답 2글자를 다 맞히는 경우를 넣어준다. 여기서 주의할 점은 user1으로 부터라는 것을 유의하여야 한다. 나는 까먹고 다 user2로 해서 equal값이 기대하는 것과 달라 오류가 났었다.

3번째에서 이겼으므로 정답블록을 확인할 수 있는 블록번호는 6이다. 이 블록6번을 확인할려면 블록 7번이 mining되어야 확인 할 수 있으므로 7번 블록까지 betAndDistribute함수를 이용해 베팅해준다.

pot에는 앞에 두명이 fail하였으므로 2 x 0.005 eth 를 가지고 있다. 3번 블록이 이겼으므로 이 pot값을 다 가져가 값이 0이 된다.

따라서 user1은 pot머니 + 베팅한 금액을 얻게 된다.

그 결과


user1이 이겨서 eth를 얻은 것을 확인 할 수 있다.


###한글자만 맞춰서 이겼을 경우

            it('should give the user the amount he bet when a single character matches', async () => {
                //한글자 맞췄을 때
                await lotery.setAnswerForTest('0xab66ec4675534645e7f83e0c9d369356db51a012a0746e2f0e82df38a345b38b', {from:deployer});
                
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //1 -> 4
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //2 -> 5
                await lotery.betAndDistribute('0xaf',{from:user1, value:betAmount}); //3 -> 6 //draw
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //4 -> 7
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //5 -> 8
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //6 -> 9

                let potBefore = await lotery.getPot(); // ==0.01eth
                let user1BalanceBefore = await web3.eth.getBalance(user1);

                let receipt7 = await lotery.betAndDistribute('0xef',{form:user2,value:betAmount}); //7 -> 10 //여기에서 확인 가능 
                let potAfter = await lotery.getPot();// draw : ==0.01 eth
                let user1BalanceAfter = await web3.eth.getBalance(user1); //==balancebefore + 0.005 eth
               
                //draw
               assert.equal(potBefore.toString(), potAfter.toString());

               user1BalanceBefore = new web3.utils.BN(user1BalanceBefore);
               assert.equal(user1BalanceBefore.add(betAmountBN).toString(), new web3.utils.BN(user1BalanceAfter).toString());
              
            })

한 글자만 맞췄을 경우에는 자신이 베팅한 금액만 다시 돌려 받으므로 pot머니는 전과 후가 같다 그리고 user1의 잔고는 베팅한 금액만 더해지면 된다.

테스트한 결과


###맞추지 못한 경우

            it.only('should get the eth of the user when the answer does not matches at all', async () => {
                // 꽝
                await lotery.setAnswerForTest('0xab66ec4675534645e7f83e0c9d369356db51a012a0746e2f0e82df38a345b38b', {from:deployer});
                
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //1 -> 4
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //2 -> 5
                await lotery.betAndDistribute('0xef',{from:user1, value:betAmount}); //3 -> 6 //fail
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //4 -> 7
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //5 -> 8
                await lotery.betAndDistribute('0xef',{from:user2, value:betAmount}); //6 -> 9

                let potBefore = await lotery.getPot(); // ==0.01eth
                let user1BalanceBefore = await web3.eth.getBalance(user1);

                let receipt7 = await lotery.betAndDistribute('0xef',{form:user2,value:betAmount}); //7 -> 10 //여기에서 확인 가능
                let potAfter = await lotery.getPot();// fail : ==0.015 eth
                let user1BalanceAfter = await web3.eth.getBalance(user1); //==balancebefore 

                //fail
                assert.equal(potBefore.add(betAmountBN).toString(), potAfter.toString());
        
                user1BalanceBefore = new web3.utils.BN(user1BalanceBefore);
                assert.equal(user1BalanceBefore.toString(), new web3.utils.BN(user1BalanceAfter).toString());
       
            });

지면 베팅한 금액이 pot에 들어간다. 테스트한 결과

2020

맨 위로 이동 ↑