ルール
- 誰でも胴元になることができる。
- 誰でも参加者として何回でもトライできる。
- 参加者は一回トライするたびに掛け金を支払い、賞金の山に加える。
- 参加者は掛け金を支払うと、1-100の目が出るサイコロを1回振る。
- 参加者はサイコロの目が予め設定された「難易度」よりも大きければ賞金の山を得る。
- 参加者が一回トライするのに必要な掛け金は延べ参加人数に比例して増える。
- 胴元ははじめの賞金を任意な額だけ用意する。
- 胴元は掛け金の一部を手数料としてもらう。
- 胴元は任意のタイミングで「難易度」を上げることができる。
- 胴元は「難易度」を下げることはできない(胴元のチートを防ぐため。)
胴元の戦略にゲーム性がありそう?
コントラクト
Solidityで実装したコントラクトは以下。
contracts/Betting.sol
pragma solidity ^0.4.7;
contract Betting{
address public manager;
uint public stake;
uint public difficulty;
function Betting() public payable{
manager = msg.sender;
stake = 1 ether;
difficulty = 0;
}
function setDifficulty(uint new_difficulty) public{
require(msg.sender == manager);
require(difficulty <= new_difficulty);
difficulty = new_difficulty;
}
function enter() public payable{
require(msg.value > stake);
stake += 1 ether;
manager.transfer(stake * 5/100);
uint r = random() % 100;
if(r >= difficulty){
msg.sender.transfer(this.balance);
}
}
function random() private view returns (uint) {
return uint(keccak256(block.difficulty, now));
}
}
テスト
テストとデプロイの環境は前回記事とだいたい同じ。
コンパイルやデプロイの実装は今回は省略。
ganacheとmochaによるローカルテストは以下の通り。
ローカルテスト後、Kovan Test Netにデプロイして動かしてみました。
test/Betting.test.js
const assert = require('assert');
const ganache= require('ganache-cli');
const Web3 = require('web3');
const web3 = new Web3(ganache.provider());
const {interface, bytecode} = require('../compile');
let betting;
let accounts;
beforeEach(async () => {
accounts = await web3.eth.getAccounts();
betting = await new web3.eth.Contract(JSON.parse(interface))
.deploy({data: bytecode})
.send({from: accounts[0], gas: '1000000', value: web3.utils.toWei('1', 'ether')});
});
describe('Betting Contract', () => {
it('deploys a contract', () => {
assert.ok(betting.options.address);
});
it('can change the difficulty', async () => {
let difficulty = await betting.methods.difficulty().call();
assert.equal(difficulty, 0);
await betting.methods.setDifficulty(50).send({
from: accounts[0],
gas: '1000000'
});
difficulty = await betting.methods.difficulty().call();
assert.equal(difficulty, 50);
});
it('cannot decrease the difficulty', async () => {
await betting.methods.setDifficulty(50).send({
from: accounts[0],
gas: '1000000'
});
try{
await betting.methods.setDifficulty(40).send({
from: accounts[0],
gas: '1000000'
});
assert(false);
}catch(err){
assert(err);
}
});
it('allows an account to enter', async () => {
const initialBalance0 = await web3.eth.getBalance(accounts[0]);
const initialBalance1 = await web3.eth.getBalance(accounts[1]);
await betting.methods.setDifficulty(100).send({
from: accounts[0],
gas: '1000000'
});
await betting.methods.enter().send({
from: accounts[1],
value: web3.utils.toWei('1.1', 'ether'),
gas: '1000000'
});
const stake = await betting.methods.stake().call();
const finalBalance0 = await web3.eth.getBalance(accounts[0]);
const finalBalance1 = await web3.eth.getBalance(accounts[1]);
const difference0 = finalBalance0 - initialBalance0;
const difference1 = finalBalance1 - initialBalance1;
assert.equal(stake, web3.utils.toWei('2', 'ether'));
assert(difference0 > 0);
assert(difference1 < 0);
});
it('sends money to the winner', async () => {
const initialBalance = await web3.eth.getBalance(accounts[1]);
await betting.methods.enter().send({
from: accounts[1],
value: web3.utils.toWei('1.1', 'ether'),
gas: '1000000'
});
const finalBalance = await web3.eth.getBalance(accounts[1]);
const difference = finalBalance - initialBalance;
assert( difference > web3.utils.toWei('0.8', 'ether'));
});
});
乱数
これらのプログラムは実用に耐えるものではありません。念のため。
疑似乱数としてKeccak関数を使ってお茶を濁したのですが、本来は乱数システムを実装すればよさそう。
参考資料
Stephen Grider, “Ethereum and Solidity: The Complete Developer's Guide”