二重Dappの作成

164093 ワード

https://www.youtube.com/playlist?list=PLlYCl1UOH8dheHS4vHOpPoHwq4Qi0R7WM
これは上の動画をもとに作成されたものです.

2-1.Dappサービス設計

  • 財布管理
  • アーキテクチャ
  • コード
    -コードを実行するにはお金がかかります.
  • 権限管理
  • ビジネスロジック更新
  • データ移行
  • 操作
    -public
    -private
  • 2-2. Lottery DomainとQueueのデザイン


    -contracts > Lottery.solを次のように変更します.
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.6.0;
    
    contract Lottery {
    
      struct BetInfo{
        uint256 answerBlockNumber;//우리가 맞추려는 정답의 블록 number
        address payable better;//정답을 맞추면 better에게 보내줘야 함. 그래서 payable을 써주ㅕ야 함.
        byte challenges;
      }
    
      address public owner;
    
    
      uint256 private _pot;//팟머니를 만들 곳
    
      constructor() public {
        owner = msg.sender;
      }
    
      function getSomeValue() public pure returns (uint value){
        return 5;
      }
    
      function getPot() public view returns (uint256 pot) {
        return _pot;
      }
    }
    
    -test > lottery.test.jsにコンテンツを追加します.
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.6.0;
    
    contract Lottery {
    
      struct BetInfo{
        uint256 answerBlockNumber;//우리가 맞추려는 정답의 블록 number
        address payable better;//정답을 맞추면 better에게 보내줘야 함. 그래서 payable을 써주ㅕ야 함.
        byte challenges;
      }
    
      address public owner;
    
    
      uint256 private _pot;//팟머니를 만들 곳
    
      constructor() public {
        owner = msg.sender;
      }
    
      function getSomeValue() public pure returns (uint value){
        return 5;
      }
    
      function getPot() public view returns (uint256 pot) {
        return _pot;
      }
    }
    
    $ npx truffle test test/lottery.test.jsやるとこうなります.

    2-3.Lottery Bet関数の実装


    https://docs.soliditylang.org/en/v0.8.9/units-and-global-variables.htmlに入ると変数がわかります.
    外部からランダムシード値を入力することもできます.

    2-3-1. Bet関数の役割


    save the bet to the queue
    キューには賭け情報が格納されます.
    お金が届いているかどうかを確認します.
    チャレンジ:一文字を打つ
  • test>lottery.test.js
  • const Lottery = artifacts.require("Lottery");
    
    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('getPot should return current pot',async()=>{
            //mochatest에서 특정 부분만 테스트할 때 only를 써주면 된다.
            let pot = await lottery.getPot();
            assert.equal(pot,0); 
        })
    })
    次にgetsomevalueを削除します.
  • contracts>Lottery.sol
  • // SPDX-License-Identifier: MIT
    pragma solidity ^0.6.0;
    
    contract Lottery {
    
      struct BetInfo{
        uint256 answerBlockNumber;//우리가 맞추려는 정답의 블록 number
        address payable bettor;//정답을 맞추면 better에게 보내줘야 함. 그래서 payable을 써주ㅕ야 함.
        byte challenges;
      }
    
      uint256 constant internal BET_BLOCK_INTERVAL = 3;
      uint256 constant internal BET_AMOUNT = 5 * 10 ** 15; 
    
      event BET(uint256 index,address bettor, uint256 amount, byte challenges, uint256 answerBlockNumber);
    
      uint256 private _tail;
      uint256 private _head;
      mapping (uint256 =>BetInfo) private _bets;//queue
      address public owner;
    
    
      uint256 private _pot;//팟머니를 만들 곳
    
      constructor() public {
        owner = msg.sender;
      }
    
      // function getSomeValue() public pure returns (uint value){
      //   return 5;
      // }
    
      function getPot() public view returns (uint256 pot) {
        return _pot;
      }
    
      function getBetInfo(uint256 index)public view returns(uint256 answerBlockNumber, address bettor, byte challenges){
        BetInfo memory b = _bets[index];
        answerBlockNumber = b.answerBlockNumber;
        bettor = b.bettor;
        challenges = b.challenges;
      } 
      function pushBet(byte challenges) internal returns (bool) {
        BetInfo memory b;
        b.bettor = msg.sender;
        b.answerBlockNumber = block.number + BET_BLOCK_INTERVAL;
        b.challenges = challenges;
    
        _bets[_tail] = b;
        _tail++;
    
        return true;
    
      }
    
      function popBet(uint256 index) internal returns (bool){
        delete _bets[index];
        return true;
        //딜리트를하면 가스가 환불이 된다. 상태 데이터베이스의 값을 없애겠다는 것임.
        //필요하지 않는 값에 대해서는 delete를 해주는 것이 맞다. 
      }
      //bet
    
      /**
      * @dev 배팅을 한다. 유저는 0.005eth를 보내야 하고 배팅용 1byte 글자를 보낸다.
      *큐에 저장된 배팅 정보는 이후 distribution 에 저장된다. 
      *@param challenges 유저가 배팅하는 글자
      *@return 함수가 잘 수행되었느지 확인하는 bool값
       */
      function bet(byte challenges) public payable returns (bool result) {
        //1. check the propter ether is sent
        require(msg.value == BET_AMOUNT,"Not enough ETH" );
    
        //2. push bet to the queue
        require(pushBet(challenges),"Fail to add a new Bet Info");
        //3. emit event log
        emit BET(_tail - 1, msg.sender, msg.value, challenges, block.number + BET_BLOCK_INTERVAL);
        
      }
    }
    //결과값을 겁ㅁ증해야 하는데 Bet 과 Distribute로 하면 될 듯
    //save the bet to the queue
    //distribute
    //r값이 틀리면 넣고 
    正しいかどうかはnpx truffleコンパイルで知ることができます.コンパイル時にbuildフォルダを削除したほうがいいです.

    2-4. Lottery Betテスト


    -test>lottery.test.js
    const Lottery = artifacts.require("Lottery");
    
    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('getPot should return current pot',async()=>{
        //     //mochatest에서 특정 부분만 테스트할 때 only를 써주면 된다.
        //     let pot = await lottery.getPot();
        //     assert.equal(pot,0); 
        // })
    
        describe('Bet',function () {
            it('shold fail when the bet money is not 0.005 ETH', async () => {
                //Fail transaction
                //스마트 컨트랙트를 만들때는 transaction object이라는 것을 줄 수가 있다.
                //transaction object {chainId, value, to from, gas(Limit), gasPrice} 
                await lottery.bet('0xab',{from :user1, value:4000000000000000})
            })
            it('should put the bet to the bet queue with 1 bet', async () => {
                //bet
    
                //check ontract balance ==0.005
    
                //check bet info betinfo에 제대로 들어갔는지 확인해봐야 한다
    
                //check log
            })
        
        
        })
    })

    表示不可

    ETH不足.
    describe('Bet',function () {
            it('shold fail when the bet money is not 0.005 ETH', async () => {
                //Fail transaction
                //스마트 컨트랙트를 만들때는 transaction object이라는 것을 줄 수가 있다.
                //transaction object {chainId, value, to from, gas(Limit), gasPrice} 
                await lottery.bet('0xab',{from :user1, value:5000000000000000})
            })
    この部分describe('Bet'~)を以下のように修正します.あと0.005で成功した.$ npx truffle test test/lottery.test.js
    このように成功したことがわかる.
    open zeppelin
    これは、最も一般的なオープンソースリファレンスライブラリのセットです.
    const Lottery = artifacts.require("Lottery");
    const assertRevert = require('./assertRever')
    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('getPot should return current pot',async()=>{
        //     //mochatest에서 특정 부분만 테스트할 때 only를 써주면 된다.
        //     let pot = await lottery.getPot();
        //     assert.equal(pot,0); 
        // })
    
        describe('Bet',function () {
            it('shold fail when the bet money is not 0.005 ETH', async () => {
                //Fail transaction
                //스마트 컨트랙트를 만들때는 transaction object이라는 것을 줄 수가 있다.
                //transaction object {chainId, value, to from, gas(Limit), gasPrice} 
                await assertRevert(lottery.bet('0xab',{from :user1, value:5000000000000000}));
                //try catch 문으로 보내서 revert가 들어있으면 제대로 잡았다. 
            })
            it.only('should put the bet to the bet queue with 1 bet', async () => {
                //bet
                await lottery.bet('0xab',{from :user1, value:5000000000000000});
                //check ontract balance ==0.005
    
                //check bet info betinfo에 제대로 들어갔는지 확인해봐야 한다
    
                //check log
            })
        
        
        })
    })
    ここで間違いがあったようですが、そのまま通り過ぎてしまいました・・・1時間捨てる
    describe部分はこのように変換すると大きなエラーが発生します.ううう
    describe('Bet',function () {
            it('shold fail when the bet money is not 0.005 ETH', async () => {
                //Fail transaction
                //스마트 컨트랙트를 만들때는 transaction object이라는 것을 줄 수가 있다.
                //transaction object {chainId, value, to from, gas(Limit), gasPrice} 
                await assertRevert(lottery.bet('0xab',{from :user1, value:4000000000000000}));
                //try catch 문으로 보내서 revert가 들어있으면 제대로 잡았다. 
            })
            it('should put the bet to the bet queue with 1 bet', async () => {
                //bet
                await lottery.bet('0xab',{from :user1, value:5000000000000000});
                //check ontract balance ==0.005
                //check bet info betinfo에 제대로 들어갔는지 확인해봐야 한다
                //check log
            })


    直った...このビデオの人は速すぎて、入力しないと思うものを入力すべきです.
    -test> assertRever.js
    module.exports = async (promise) => {
        try {
            await promise;
            assert.fail('Expected revert not received');
        } catch (error) {
            const revertFound = error.message.search('revert') >= 0;
            assert(revertFound, `Expected "revert", got ${error} instead`);
        }
    }

    required assert差異
    https://steemit.com/kr/@ryugihyeok/kr-revert-assert-require
    requireは論理に合致しなければ直ちに論理を終了し,assertは条件に合致しなくても常に関数を実行する.したがって、assertは、変更後のステータスをチェックしたり、絶対に不可能な論理をチェックしたり、オーバーフローやバックフローをチェックしたりするために使用されます.
    -test> lottery.test.js
    receingという変数consoleに入れます.ロゴで撮ってみよう
       it('should put the bet to the bet queue with 1 bet', async () => {
                //bet
                let receipt = await lottery.bet('0xab',{from :user1, value:5000000000000000});
                console.log(receipt);
                //check ontract balance ==0.005
    
                //check bet info betinfo에 제대로 들어갔는지 확인해봐야 한다
    
                //check log
            })

    ここからtx hash値の出現が見られ,gasusedによってどれだけのガスが消費されたか,logsによってどのようなアクティビティが実行されたかが見られる.
    最終コード
  • test >lottery.test.js
  • const Lottery = artifacts.require("Lottery");
    const assertRevert = require('./assertRever');
    const expectEvent = require('./expectEvent')
    
    
    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('getPot should return current pot',async()=>{
        //     //mochatest에서 특정 부분만 테스트할 때 only를 써주면 된다.
        //     let pot = await lottery.getPot();
        //     assert.equal(pot,0); 
        // })
    
        describe.only('Bet',function () {
            it('shold fail when the bet money is not 0.005 ETH', async () => {
                //Fail transaction
                //스마트 컨트랙트를 만들때는 transaction object이라는 것을 줄 수가 있다.
                //transaction object {chainId, value, to from, gas(Limit), gasPrice} 
                await lottery.bet('0xab',{from :user1, value:4000000000000000});
                //try catch 문으로 보내서 revert가 들어있으면 제대로 잡았다. 
            })
            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
                let contractBalance = await web3.eth.getBalance(lottery.address);
                assert.equal(contractBalance,betAmount);
    
                //check bet info betinfo에 제대로 들어갔는지 확인해봐야 한다
                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');
                //check log
                // console.log(receipt.logs
            await expectEvent.inLogs(receipt.logs,'BET');        
            })
        
        
        })
    })
    
  • test>assertRever.js
  • module.export = async(promise)=>{
        try{
            await promise;
            assert.fail('Expectee rever not recedived');
        }catch(error){
            const revertFound = error.message.search('revert') >= 0;
            assert(revertFound, `Expected "revert", got ${error} instead`);
        }
    }
  • test >expectEvent.js
  • const assert= require('chai').assert;
    //npm i chai
    
    const inLogs = async (logs,eventName)=>{
        const event = logs.find(e=>e.event === eventName);
        assert.exists(event);
    }
    module.exports = {
        inLogs
    }

    2-5. calculate Etherume GAS


    2-5-1.n/a.手数料

  • gas
    gaslimitのように
  • gasPrice
    -ETH
  • 手数料=gas*ガス価格
  • 2-5-2. 関数あたり消費されるGAS数

  • 32バイトが新たに貯蔵され、20000ガスを消費>無限に貯蔵できない.
  • 32バイトが既存変数の値を置き換えるときに5000ガスを消費する
  • .
  • 既存変数を初期化し、使用しない場合10000ガスは
  • に戻る.
    私たちが使用しているbetfunctionでは、1回目の実行には90846 gasが必要ですが、2回目からは75846 gasを減らす必要があります.
    取引を誘発するには基本的に21000ガスを消費しなければならない.また、励起イベント値も375ガスを消費する必要があり、変数値も375ガスを1つずつ消費する必要がある.

    2-6. Distribute機能の設計


    2-6-1. Distribute関数の役割


    正解をチェックし、正解者にお金を返し、hotmoneyにお金を預けます.」

    2-6-2. getBlockStatus()


    この関数はブロック番号でBlockStatusを返す関数です.
    1.現在入力されているデータ・ブロックが「検索するデータ・ブロックの番号」より大きく、「検索するデータ・ブロックの番号」プラス256より少ない場合、ハッシュ値が見つかるため、BlockStatus.Checkableに戻る.
    2.現在、ブロックの番号が私たちが探しているブロックの番号以下である場合は、さらに掘り起こす必要があります.
    3.現在のブロック番号が「私たちが探しているブロック番号」に限界値245を加えた場合、数量を比較できないので返却します.BlockStatus.BlockLimitPassedを返却します.
    enum BlockStatus {Checkable, NotRevealed, BlockLimitPassed};
    
    function getBlockStatus(uint answerBlockNumber) internal view returns (BlockStatus){
        if(block.number > answerBlockNumber && block.number < BLOCK_LIMIT +  answerBlockNumber) {
          return BlockStatus.Checkable;
        }
        if(block.number<=answerBlockNumber){
          return BlockStatus.NotRevealed;
        }
        if(block.number >= answerBlockNumber + BLOCK_LIMIT) {
          return BlockStatus.BlockLimitPassed;
        }
        return BlockStatus.BlockLimitPassed;
      }

    2-6-3. distribute()


    betsの内容はfor文で返され,各要素の状態によって状況の数を区別する.
    function distribute() public {
        uint256 cur;
        BetInfo memory b;
        BlockStatus currentBlockStatus;
        for(cur=_head; cur<_tail; cur++){
            b = _bets[cur];
            currentBlockStatus = getBlockStatus(b.answerBlockNumber);
          //Checkable: block.number > AnswerBlockNumber && block.number < BLOCK_LIMIT + AnswerBlockNumber 1
          if(currentBlockStatus == BlockStatus.Checkable){
            //if win, bettor gets pot
    
            //if fail, bettor's money goes pot
    
            //if draw, refund bettor's money
    
          //Not Revealed: block.number <= AnswerBlockNumber 2
          if(currentBlockStatus == BlockStatus.NotRevealed) {
            break;//아직 마이닝이 되지 않았다.
          }
    
          //Block Limit Passed : block.number >= AnswerBlockNumber+ Block_LiMIT 3
          if(currentBlockStatus == BlockStatus.BlockLimitPassed){
            //refund
            //emit refund
          }
    
          }
    
          //Not Revealed
    
          //Block Limit Passed
        }
      }
    Enum配列

    2-7. Lottery isMatch関数の実装とテスト


    上の値が比較できると、3つの場合の数がわかります.

  • 値が正しい場合

  • すべての値が整列できない場合.

  • 1つの値しか一致しない場合
    今はisMatch関数で分けましょう
  • 2-7-1. Lottery isMatch関数の実装


    -contracts>lottery.solに次のセクションを追加します.
      enum BettingResult { Fail, Win,Draw }
    
      /**
      *@dev 배팅 글자와 정답을 확인한다.
      *@param challenges 배팅 글자
      *@param answer 블록해시
      *@return 정답 결과
      */
    
    
    
      function isMatch(byte challenges, bytes32 answer) public pure returns (BettingResult) {
        //challenges  에는 0xab이렇게 들어올 것이다.ㅣ 
        //answer 0xab...........ff 32byte로 들어올 것이다.ㅣ 
        byte c1 = challenges;
        byte c2 = challenges;
    
        byte a1 = answer[0];
        byte a2 = answer[0];
    
        //Get first number
        c1 = c1 >> 4; 
        c1 = c1 << 4;
    
        //0xab=>0x0a=>0xa0
    
        a1 = a1 >> 4;
        a1 = a1 << 4;
        
        //Get Second Number
        c2 = c2 << 4;
        c2 = c2 >> 4;
    
        a2 = a2 << 4;
        a2 = a2 >> 4;
        //0xab => 0xb0 =>0x0b;
      
        if(a1 == c1 && a2 == c2) {
          return BettingResult.Win;
        }
        if(a1 == c1 || a2 == c2) {
          return BettingResult.Draw;
        }
        return BettingResult.Fail;
    
      
      }
    enum配列なので、実際には0、1、2なので、こう返します.
    shift
    バイナリシフトと16進数シフト
    SHIFT演算はバイナリ数を基準に考えやすい.
    0 b 00001を左に移動させると...0b0000010.
    0 b 00001を2に左に移動すると...0b0000100.
    0 b 00001を4に左に移動すると...0b000000000.
    では、16進数で...
    0 x 00001を1に切り替えると...0 x 00000000 2になります.
    0 x 00001を3に切り替えると...0 x 0000008になります.
    0 x 00001を4に切り替えると...0x0000010.
    0 x 000001を0 x 00000100に変更するには、4を切り替えます.
    0 x 0000001を0 x 000000100に変更するには、8を切り替えます.
    0 x 0000001を0 x 000001000に変更するには、12を切り替えます.
    簡単な原理ですが...つまり、16進数を基準として、Shiftが1つの数字になった場合は、(4*桁)のようにShift!
    を使用して編集できます.npx truffle compile

    2-7-2. テスト


    まずハッシュ値として値を探します.ターミナルウィンドウに入力:$ web3.eth.getBlockCount() $ web3.eth.getBlock(372)
    上記のように$ truffle consoleを打てばいいです.
  • test > lottery.test.js
    次のコードを追加します.
  • describe.only('isMatch',function(){
            let blockHash = "0xd3b8938e583c758a102be9b53419707f3258044a01c15be194a4a74690eedf82";
            it('should be BettingResult',async () =>{           
                let matchingResult = await lottery.isMatch('0xd3',blockHash);
                assert.equal(matchingResult,1);
            })
    
            it('should be BettingResult.Fail',async()=>{
                let matchingResult = await lottery.isMatch('0xf2',blockHash);
                assert.equal(matchingResult,0);
            })
            it('shold be BettingResult.Draw',async()=>{
                let matchingResult = await lottery.isMatch('0xd2',blockHash);
                assert.equal(matchingResult,2);
            })
        })

    2-8. 分散関数の実装


    2-8-1. 配備モード、テストモードの設定


    blockhashを使用してテストする必要があります.ランダムに与えられた値はテストしにくいです.したがって、導入とテストのためのモデルをそれぞれ開発します.」

    2-8-2. ethを転送する方法


    call
  • の他のスマート約定関数を呼び出すこともできます.
  • は深刻な安全問題になる可能性があります.
  • コール
  • インテリジェント会議でない場合は、使用しないほうがいいです.
  • send
  • false
  • に送金
    transfer
  • 送金、ダメなら取引は失敗します.
  • 最も多く使われているのは
  • です.最も安全です.
  • transferではなくsendまたはcallを使用してethを転送するには、送信前にステータス値から値を減算することが望ましい.

    2-8-3. これまでのコード

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.6.0;
    
    contract Lottery {
    
      struct BetInfo{
        uint256 answerBlockNumber;//우리가 맞추려는 정답의 블록 number
        address payable bettor;//정답을 맞추면 better에게 보내줘야 함. 그래서 payable을 써주ㅕ야 함.
        byte challenges;
      }
      uint256 constant internal BLOCK_LIMIT = 256;
      uint256 constant internal BET_BLOCK_INTERVAL = 3;
      uint256 constant internal BET_AMOUNT = 5 * 10 ** 15; 
    
      enum BlockStatus {Checkable, NotRevealed, BlockLimitPassed}
    
      uint256 private _tail;
      uint256 private _head;
      mapping (uint256 => BetInfo) private _bets;//queue
      address payable public owner;
    
      
    
      uint256 private _pot;//팟머니를 만들 곳
    
      constructor() public {
        owner = msg.sender;
      }
    
      // function getSomeValue() public pure returns (uint value){
      //   return 5;
      // }
      event BET(uint256 index,address indexed bettor, uint256 amount, byte challenges, uint256 answerBlockNumber);
      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 getPot() public view returns (uint256 pot) {
        return _pot;
      }
    
      function getBetInfo(uint256 index)public view returns (uint256 answerBlockNumber, address bettor, byte challenges) {
        BetInfo memory b = _bets[index];
        answerBlockNumber = b.answerBlockNumber;
        bettor = b.bettor;
        challenges = b.challenges;
      } 
      function pushBet(byte challenges) internal returns (bool) {
        BetInfo memory b;
        b.bettor = msg.sender;
        b.answerBlockNumber = block.number + BET_BLOCK_INTERVAL;
        b.challenges = challenges;
    
        _bets[_tail] = b;
        _tail++;
    
        return true;
    
      }
    
      function popBet(uint256 index) internal returns (bool){
        delete _bets[index];
        return true;
        //딜리트를하면 가스가 환불이 된다. 상태 데이터베이스의 값을 없애겠다는 것임.
        //필요하지 않는 값에 대해서는 delete를 해주는 것이 맞다. 
      }
      //bet
    
      /**
      * @dev 배팅을 한다. 유저는 0.005eth를 보내야 하고 배팅용 1byte 글자를 보낸다.
      *큐에 저장된 배팅 정보는 이후 distribution 에 저장된다. 
      *@param challenges 유저가 배팅하는 글자
      *@return 함수가 잘 수행되었느지 확인하는 bool값
       */
      function bet(byte challenges) public payable returns (bool result) {
        //1. check the propter ether is sent
        require(msg.value == BET_AMOUNT,"Not enough ETH" );
    
        //2. push bet to the queue
        require(pushBet(challenges),"Fail to add a new Bet Info");
        //3. emit event log
        emit BET(_tail - 1, msg.sender, msg.value , challenges, block.number + BET_BLOCK_INTERVAL);
        return true;
      }
      bool private mode = false;// false:devmod
      bytes32 public answerForTest;
      //정답을 지정할 수 있게 setterFunction 도 만들어준다.ㅣ 
      function setAnswerForTest(bytes32 answer) public returns (bool result){
        answerForTest = answer;
        return true;
      }
    
      function getAnswerBlockHash(uint256 answerBlockNumber) internal view returns (bytes32 answer){
        require(msg.sender ==owner, "Only owner can set the answer ");
        return mode ?  blockhash(answerBlockNumber): answerForTest;
      } 
    
    
    
      /**
      *@dev 배팅 결과값을 확인하고 팟머니를 분배한다. 
      *정답실패: 팟머니 축적, 정답 맞춤: 팟머니 획득, 한글자 맞춤 or 정답 확인 불가: 배팅 금액만 획득
      */
       function distribute() public {
        uint256 cur;
        uint256 transferAmount; // 이유: 얼마나 보냈는지 찍기 위해서
    
        BetInfo memory b;
        BlockStatus currentBlockStatus;
        BettingResult currentBettingResult;
        for(cur=_head; cur<_tail; cur++){
            b = _bets[cur];
            currentBlockStatus = getBlockStatus(b.answerBlockNumber);
          //Checkable: block.number > AnswerBlockNumber && block.number < BLOCK_LIMIT + AnswerBlockNumber 1
            if(currentBlockStatus == BlockStatus.Checkable){
              bytes32 answerBlockHash = getAnswerBlockHash(b.answerBlockNumber);
              currentBettingResult = isMatch(b.challenges,answerBlockHash);//결과값을 가져옴
            //if win, bettor gets pot
              if(currentBettingResult == BettingResult.Win){
                  //transfet pot/수수료를 떼가는 함수 만들자 trnasferAfterPaingFee
                  transferAfterPayingFee(b.bettor, _pot + BET_AMOUNT);//아직 내가 배팅한 금액은 추가되지 않았기 때문에
                  //pot = 9
                  _pot = 0;
                  //transfer여서 이렇게 쓰는 것임
    
                  //emit Win
                  emit WIN(cur, b.bettor, transferAmount, b.challenges,answerBlockHash[0], b.answerBlockNumber);
              }
            //if fail, bettor's money goes pot
              if(currentBettingResult == BettingResult.Fail){
                  //pot = pot + BET_AMOUNT
                  _pot += BET_AMOUNT;
                  //emit FAIL
                  emit FAIL(cur, b.bettor, 0, b.challenges,answerBlockHash[0], b.answerBlockNumber);
              }
            //if draw, 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);
              }
            //Not Revealed: block.number <= AnswerBlockNumber 2
            if(currentBlockStatus == BlockStatus.NotRevealed) {
              break;//아직 마이닝이 되지 않았다.
            }
    
            //Block Limit Passed : block.number >= AnswerBlockNumber+ Block_LiMIT 3
            if(currentBlockStatus == BlockStatus.BlockLimitPassed){
              //refund
              transferAmount = transferAfterPayingFee(b.bettor, BET_AMOUNT);
    
              //emit refund
              emit REFUND(cur, b.bettor, transferAmount, b.challenges, b.answerBlockNumber);
            }
            popBet(cur);
    
          }
          _head = cur;
    
          //Not Revealed
    
          //Block Limit Passed
        }
      }
      function transferAfterPayingFee(address payable addr, uint256 amount) internal returns (uint256){
        // uint256 fee = amount /100; 
        uint256 fee = 0;
        uint256 amountWithoutFee = amount -fee;
        //transfer to addr
        addr.transfer(amountWithoutFee);
        //transfer to owner
        owner.transfer(fee);
        return amountWithoutFee;
      
      }
      function getBlockStatus(uint answerBlockNumber) internal view returns (BlockStatus){
        if(block.number > answerBlockNumber && block.number < BLOCK_LIMIT +  answerBlockNumber) {
          return BlockStatus.Checkable;
        }
        if(block.number<=answerBlockNumber){
          return BlockStatus.NotRevealed;
        }
        if(block.number >= answerBlockNumber + BLOCK_LIMIT) {
          return BlockStatus.BlockLimitPassed;
        }
        return BlockStatus.BlockLimitPassed;
      }
      enum BettingResult { Fail, Win,Draw }
    
      /**
      *@dev 배팅 글자와 정답을 확인한다.
      *@param challenges 배팅 글자
      *@param answer 블록해시
      *@return 정답 결과
      */
    
    
    
      function isMatch(byte challenges, bytes32 answer) public pure returns (BettingResult) {
        //challenges  에는 0xab이렇게 들어올 것이다.ㅣ 
        //answer 0xab...........ff 32byte로 들어올 것이다.ㅣ 
        byte c1 = challenges;
        byte c2 = challenges;
    
        byte a1 = answer[0];
        byte a2 = answer[0];
    
        //Get first number
        c1 = c1 >> 4; 
        c1 = c1 << 4;
    
        //0xab=>0x0a=>0xa0
    
        a1 = a1 >> 4;
        a1 = a1 << 4;
        
        //Get Second Number
        c2 = c2 << 4;
        c2 = c2 >> 4;
    
        a2 = a2 << 4;
        a2 = a2 >> 4;
        //0xab => 0xb0 =>0x0b;
      
        if(a1 == c1 && a2 == c2) {
          return BettingResult.Win;
        }
        if(a1 == c1 || a2 == c2) {
          return BettingResult.Draw;
        }
        return BettingResult.Fail;
    
      
      }
    }
     
    わあ...本当に疲れたしかし、もう一つあります.ううう
    変更が多すぎて直接アップロード $ npx truffle compile
    コンパイル成功...!

    2-9. Testing distribute()


    mocha test
    https://jeonghwan-kim.github.io/mocha/
    evm mineという関数を使用してtruffleでマイニングできます.

    2-9-1.contracts>Lottery.sol


    コードが間違っていたので、ハブのコードをアップロードしました.
    間違いを探さなければならない.
    pragma solidity ^0.6.0;
    
    
    contract Lottery {
        struct BetInfo {
            uint256 answerBlockNumber;
            address payable bettor;
            byte challenges;
        }
        
        uint256 private _tail;
        uint256 private _head;
        mapping (uint256 => BetInfo) private _bets;
    
        address payable public owner;
        
        
        uint256 private _pot;
        bool private mode = false; // false : use answer for test , true : use real block hash
        bytes32 public answerForTest;
    
        uint256 constant internal BLOCK_LIMIT = 256;
        uint256 constant internal BET_BLOCK_INTERVAL = 3;
        uint256 constant internal BET_AMOUNT = 5 * 10 ** 15;
    
        enum BlockStatus {Checkable, NotRevealed, BlockLimitPassed}
        enum BettingResult {Fail, Win, Draw}
    
        event BET(uint256 index, address indexed bettor, uint256 amount, byte challenges, uint256 answerBlockNumber);
        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);
    
        constructor() public {
            owner = msg.sender;
        }
    
        function getPot() public view returns (uint256 pot) {
            return _pot;
        }
    
        /**
         * @dev 베팅과 정답 체크를 한다. 유저는 0.005 ETH를 보내야 하고, 베팅용 1 byte 글자를 보낸다.
         * 큐에 저장된 베팅 정보는 이후 distribute 함수에서 해결된다.
         * @param challenges 유저가 베팅하는 글자
    
         */
        function betAndDistribute(byte challenges) public payable returns (bool result) {
            bet(challenges);
    
            distribute();
    
            return true;
        }
    
        // 90846 -> 75846
        /**
         * @dev 베팅을 한다. 유저는 0.005 ETH를 보내야 하고, 베팅용 1 byte 글자를 보낸다.
         * 큐에 저장된 베팅 정보는 이후 distribute 함수에서 해결된다.
         * @param challenges 유저가 베팅하는 글자
         */
        function bet(byte 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;
        }
    
        /**
         * @dev 베팅 결과값을 확인 하고 팟머니를 분배한다.
         * 정답 실패 : 팟머니 축척, 정답 맞춤 : 팟머니 획득, 한글자 맞춤 or 정답 확인 불가 : 베팅 금액만 획득
         */
        function distribute() public {
            // head 3 4 5 6 7 8 9 10 11 12 tail
            uint256 cur;
            uint256 transferAmount;
    
            BetInfo memory b;
            BlockStatus currentBlockStatus;
            BettingResult currentBettingResult;
    
            for(cur=_head;cur<_tail;cur++) {
                b = _bets[cur];
                currentBlockStatus = getBlockStatus(b.answerBlockNumber);
                // Checkable : block.number > AnswerBlockNumber && block.number  <  BLOCK_LIMIT + AnswerBlockNumber 1
                if(currentBlockStatus == BlockStatus.Checkable) {
                    bytes32 answerBlockHash = getAnswerBlockHash(b.answerBlockNumber);
                    currentBettingResult = isMatch(b.challenges, answerBlockHash);
                    // if 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);
                    }
                    // if fail, bettor's money goes pot
                    if(currentBettingResult == BettingResult.Fail) {
                        // pot = pot + BET_AMOUNT
                        _pot += BET_AMOUNT;
                        // emit FAIL
                        emit FAIL(cur, b.bettor, 0, b.challenges, answerBlockHash[0], b.answerBlockNumber);
                    }
                    
                    // if draw, 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);
                    }
                }
    
                // Not Revealed : block.number <= AnswerBlockNumber 2
                if(currentBlockStatus == BlockStatus.NotRevealed) {
                    break;
                }
    
                // Block Limit Passed : block.number >= AnswerBlockNumber + BLOCK_LIMIT 3
                if(currentBlockStatus == BlockStatus.BlockLimitPassed) {
                    // refund
                    transferAmount = transferAfterPayingFee(b.bettor, BET_AMOUNT);
                    // emit refund
                    emit REFUND(cur, b.bettor, transferAmount, b.challenges, b.answerBlockNumber);
                }
    
                popBet(cur);
            }
            _head = cur;
        }
    
        function transferAfterPayingFee(address payable addr, uint256 amount) internal returns (uint256) {
            
            // uint256 fee = amount / 100;
            uint256 fee = 0;
            uint256 amountWithoutFee = amount - fee;
    
            // transfer to addr
            addr.transfer(amountWithoutFee);
    
            // transfer to owner
            owner.transfer(fee);
    
            return amountWithoutFee;
        }
    
        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;
        }
    
        function getAnswerBlockHash(uint256 answerBlockNumber) internal view returns (bytes32 answer) {
            return mode ? blockhash(answerBlockNumber) : answerForTest;
        }
    
        /**
         * @dev 베팅글자와 정답을 확인한다.
         * @param challenges 베팅 글자
         * @param answer 블락해쉬
         * @return 정답결과
         */
        function isMatch(byte challenges, bytes32 answer) public pure returns (BettingResult) {
            // challenges 0xab
            // answer 0xab......ff 32 bytes
    
            byte c1 = challenges;
            byte c2 = challenges;
    
            byte a1 = answer[0];
            byte a2 = answer[0];
    
            // Get first number
            c1 = c1 >> 4; // 0xab -> 0x0a
            c1 = c1 << 4; // 0x0a -> 0xa0
    
            a1 = a1 >> 4;
            a1 = a1 << 4;
    
            // Get Second number
            c2 = c2 << 4; // 0xab -> 0xb0
            c2 = c2 >> 4; // 0xb0 -> 0x0b
    
            a2 = a2 << 4;
            a2 = a2 >> 4;
    
            if(a1 == c1 && a2 == c2) {
                return BettingResult.Win;
            }
    
            if(a1 == c1 || a2 == c2) {
                return BettingResult.Draw;
            }
    
            return BettingResult.Fail;
    
        }
    
        function getBlockStatus(uint256 answerBlockNumber) internal view returns (BlockStatus) {
            if(block.number > answerBlockNumber && block.number  <  BLOCK_LIMIT + answerBlockNumber) {
                return BlockStatus.Checkable;
            }
    
            if(block.number <= answerBlockNumber) {
                return BlockStatus.NotRevealed;
            }
    
            if(block.number >= answerBlockNumber + BLOCK_LIMIT) {
                return BlockStatus.BlockLimitPassed;
            }
    
            return BlockStatus.BlockLimitPassed;
        }
        
    
        function getBetInfo(uint256 index) public view returns (uint256 answerBlockNumber, address bettor, byte challenges) {
            BetInfo memory b = _bets[index];
            answerBlockNumber = b.answerBlockNumber;
            bettor = b.bettor;
            challenges = b.challenges;
        }
    
        function pushBet(byte challenges) internal returns (bool) {
            BetInfo memory b;
            b.bettor = msg.sender; // 20 byte
            b.answerBlockNumber = block.number + BET_BLOCK_INTERVAL; // 32byte  20000 gas
            b.challenges = challenges; // byte // 20000 gas
    
            _bets[_tail] = b;
            _tail++; // 32byte 값 변화 // 20000 gas -> 5000 gas
    
            return true;
        }
    
        function popBet(uint256 index) internal returns (bool) {
            delete _bets[index];
            return true;
        }
    }

    2-9-2.test>lottery.test.js

    describe('Distribute',function () {
            describe('When the answer is checkable', function () {
                it.only('should give the user the pot when the answer matches',async ()=>{
                    await lottery.setAnswerForTest('0xd3b8938e583c758a102be9b53419707f3258044a01c15be194a4a74690eedf82',{from:deployer});
                    await lottery.betAndDistribute('0xef',{from:user2,value:betAmount});
                    await lottery.betAndDistribute('0xef',{from:user2,value:betAmount});
                    await lottery.betAndDistribute('0xd3',{from:user1,value:betAmount});
                    await lottery.betAndDistribute('0xef',{from:user2,value:betAmount});
                    await lottery.betAndDistribute('0xef',{from:user2,value:betAmount});
                    await lottery.betAndDistribute('0xef',{from:user2,value:betAmount});
                    
                    let potBefore = await lottery.getPot();//0.01ETH
                    // console.log(`value :`,potBefore.toString())
                    let user1BalanceBefore = await web3.eth.getBalance(user1);
                    
                    await lottery.betAndDistribute('0xef',{from:user2,value:betAmount});//이 때 user1에게 pot이 간다.ㅣ
    
                    let potAfter = await lottery.getPot();//0
                    let user1BalanceAfter = await web3.eth.getBalance(user1);
    
                    //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());
                } )
            })
            describe('When the answer is not revealed(Not Minded',function (){
    
            })
            describe('When the answer is not revealed(Block limit is passed',function (){
    
            })
        })