二重Dappの作成
164093 ワード
https://www.youtube.com/playlist?list=PLlYCl1UOH8dheHS4vHOpPoHwq4Qi0R7WM
これは上の動画をもとに作成されたものです.
財布管理 アーキテクチャ コード
-コードを実行するにはお金がかかります. 権限管理 ビジネスロジック更新 データ移行 操作
-public
-private 2-2. Lottery DomainとQueueのデザイン
test>lottery.test.js contracts>Lottery.sol
2-4. Lottery Betテスト
test >lottery.test.js test>assertRever.js test >expectEvent.js 2-5. calculate Etherume GAS
gas
gaslimitのように gasPrice
-ETH 手数料=gas*ガス価格 32バイトが新たに貯蔵され、20000ガスを消費>無限に貯蔵できない. 32バイトが既存変数の値を置き換えるときに5000ガスを消費する .既存変数を初期化し、使用しない場合10000ガスは に戻る.
私たちが使用しているbetfunctionでは、1回目の実行には90846 gasが必要ですが、2回目からは75846 gasを減らす必要があります.
取引を誘発するには基本的に21000ガスを消費しなければならない.また、励起イベント値も375ガスを消費する必要があり、変数値も375ガスを1つずつ消費する必要がある.
正解をチェックし、正解者にお金を返し、hotmoneyにお金を預けます.」
この関数はブロック番号でBlockStatusを返す関数です.
1.現在入力されているデータ・ブロックが「検索するデータ・ブロックの番号」より大きく、「検索するデータ・ブロックの番号」プラス256より少ない場合、ハッシュ値が見つかるため、
2.現在、ブロックの番号が私たちが探しているブロックの番号以下である場合は、さらに掘り起こす必要があります.
3.現在のブロック番号が「私たちが探しているブロック番号」に限界値245を加えた場合、数量を比較できないので返却します.
betsの内容はfor文で返され,各要素の状態によって状況の数を区別する.
2-7. Lottery isMatch関数の実装とテスト
値が正しい場合
すべての値が整列できない場合.
1つの値しか一致しない場合
今はisMatch関数で分けましょう
-contracts>lottery.solに次のセクションを追加します.
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!
を使用して編集できます.
まずハッシュ値として値を探します.ターミナルウィンドウに入力:
上記のように test > lottery.test.js
次のコードを追加します.
2-8. 分散関数の実装
の他のスマート約定関数を呼び出すこともできます. は深刻な安全問題になる可能性があります. コールインテリジェント会議でない場合は、使用しないほうがいいです. send false に送金
transfer送金、ダメなら取引は失敗します. 最も多く使われているのはです.最も安全です. transferではなくsendまたはcallを使用してethを転送するには、送信前にステータス値から値を減算することが望ましい.
変更が多すぎて直接アップロード
コンパイル成功...!
2-9. Testing distribute()
これは上の動画をもとに作成されたものです.
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
キューには賭け情報が格納されます.
お金が届いているかどうかを確認します.
チャレンジ:一文字を打つ
// 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;
}
}
// 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;
}
}
https://docs.soliditylang.org/en/v0.8.9/units-and-global-variables.htmlに入ると変数がわかります.
外部からランダムシード値を入力することもできます.
2-3-1. Bet関数の役割
save the bet to the queue
キューには賭け情報が格納されます.
お金が届いているかどうかを確認します.
チャレンジ:一文字を打つ
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を削除します.// 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.jsconst 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.jsmodule.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によってどのようなアクティビティが実行されたかが見られる.
最終コード
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
})
})
})
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})
})
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
})
})
})
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
})
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`);
}
}
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
})
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');
})
})
})
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`);
}
}
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.手数料
gaslimitのように
-ETH
2-5-2. 関数あたり消費されるGAS数
私たちが使用している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
を打てばいいです.次のコードを追加します.
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
transfer
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 (){
})
})
Reference
この問題について(二重Dappの作成), 我々は、より多くの情報をここで見つけました
https://velog.io/@nara7875/2강
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
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;
}
}
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 (){
})
})
Reference
この問題について(二重Dappの作成), 我々は、より多くの情報をここで見つけました https://velog.io/@nara7875/2강テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol