pragma solidity ^0.4.19; import './SafeMath.sol'; import './oraclizeLib.sol'; //contract DogRace is usingOraclize { contract DogRace { using SafeMath for uint256; string public constant version = "0.0.4"; uint public constant min_bet = 0.1 ether; uint public constant max_bet = 1 ether; uint public constant house_fee_pct = 5; uint public constant claim_period = 30 days; address public owner; // owner address // Currencies: BTC, ETH, LTC, BCH, XRP uint8 constant dogs_count = 5; // Race states and timing struct chronus_struct { bool betting_open; // boolean: check if betting is open bool race_start; // boolean: check if race has started bool race_end; // boolean: check if race has ended bool race_voided; // boolean: check if race has been voided uint starting_time; // timestamp of when the race starts uint betting_duration; // duration of betting period uint race_duration; // duration of the race } // Single bet information struct bet_info { uint8 dog; // Dog on which the bet is made uint amount; // Amount of the bet } // Dog pool information struct pool_info { uint bets_total; // total bets amount uint pre; // start price uint post; // ending price int delta; // price delta bool post_check; // differentiating pre and post prices in oraclize callback bool winner; // has respective dog won the race? } // Bettor information struct bettor_info { uint bets_total; // total bets amount bool rewarded; // if reward was paid to the bettor bet_info[] bets; // array of bets } mapping (bytes32 => uint) oraclize_query_ids; // mapping oraclize query IDs => dogs mapping (address => bettor_info) bettors; // mapping bettor address => bettor information pool_info[dogs_count] pools; // pools for each currency chronus_struct chronus; // states and timing uint public bets_total = 0; // total amount of bets uint public reward_total = 0; // total amount to be distributed among winners uint public winning_bets_total = 0; // total amount of bets in winning pool(s) uint prices_remaining = dogs_count; // variable to check if all prices are received at the end of the race int max_delta = int256((uint256(1) << 255)); // winner dog(s) delta; initialize to minimal int value // tracking events event OraclizeQuery(string description); event PriceTicker(uint dog, uint price); event Bet(address from, uint256 _value, uint dog); event Reward(address to, uint256 _value); event CheckPoint(string what); // constructor function DogRace() public { owner = msg.sender; oraclizeLib.oraclize_setCustomGasPrice(20000000000 wei); // 20GWei } // modifiers for restricting access to methods modifier onlyOwner { require(owner == msg.sender); _; } modifier duringBetting { require(chronus.betting_open); _; } modifier beforeBetting { require(!chronus.betting_open && !chronus.race_start); _; } modifier afterRace { require(chronus.race_end); _; } // ======== Bettor interface =============================================================================================== // place a bet function place_bet(uint8 dog) external duringBetting payable { require(msg.value >= min_bet && msg.value <= max_bet && dog < dogs_count); bet_info memory current_bet; // Update bettors info current_bet.amount = msg.value; current_bet.dog = dog; bettors[msg.sender].bets.push(current_bet); bettors[msg.sender].bets_total = bettors[msg.sender].bets_total.add(msg.value); // Update pools info pools[dog].bets_total = pools[dog].bets_total.add(msg.value); bets_total = bets_total.add(msg.value); Bet(msg.sender, msg.value, dog); } // fallback method for accepting payments function () private payable {} // method to check the reward amount function check_reward() afterRace external constant returns (uint) { return bettor_reward(msg.sender); } // method to claim the reward function claim_reward() afterRace external { require(!bettors[msg.sender].rewarded); uint reward = bettor_reward(msg.sender); require(reward > 0 && this.balance >= reward); bettors[msg.sender].rewarded = true; msg.sender.transfer(reward); Reward(msg.sender, reward); } // ============================================================================================================================ //oraclize callback method function __callback(bytes32 myid, string result) public { require (msg.sender == oraclizeLib.oraclize_cbAddress()); chronus.race_start = true; chronus.betting_open = false; uint dog_index = oraclize_query_ids[myid]; require(dog_index > 0); // Check if the query id is known dog_index--; oraclize_query_ids[myid] = 0; // Prevent duplicate callbacks if (!pools[dog_index].post_check) { pools[dog_index].pre = oraclizeLib.parseInt(result, 3); // from Oraclize pools[dog_index].post_check = true; // next check for the coin will be ending price check PriceTicker(dog_index, pools[dog_index].pre); } else { pools[dog_index].post = oraclizeLib.parseInt(result, 3); // from Oraclize // calculating the difference in price with a precision of 5 digits pools[dog_index].delta = int(pools[dog_index].post - pools[dog_index].pre) * 10000 / int(pools[dog_index].pre); if (max_delta < pools[dog_index].delta) { max_delta = pools[dog_index].delta; } PriceTicker(dog_index, pools[dog_index].post); prices_remaining--; // How many end prices are to be received if (prices_remaining == 0) { // If all end prices have been received, then process rewards end_race(); } } } // calculate bettor's reward function bettor_reward(address candidate) internal afterRace constant returns(uint reward) { bettor_info storage bettor = bettors[candidate]; if (chronus.race_voided) { reward = bettor.bets_total; } else { if (reward_total == 0) { return 0; } uint winning_bets = 0; for (uint i = 0; i < bettor.bets.length; i++) { if (pools[bettor.bets[i].dog].winner) { winning_bets = winning_bets.add(bettor.bets[i].amount); } } reward = reward_total.mul(winning_bets).div(winning_bets_total); } } // ============= DApp interface ============================================================================================== // exposing pool details for DApp function get_pool(uint dog) external constant returns (uint, uint, uint, int, bool, bool) { return (pools[dog].bets_total, pools[dog].pre, pools[dog].post, pools[dog].delta, pools[dog].post_check, pools[dog].winner); } // exposing chronus for DApp function get_chronus() external constant returns (bool, bool, bool, bool, uint, uint, uint) { return (chronus.betting_open, chronus.race_start, chronus.race_end, chronus.race_voided, chronus.starting_time, chronus.betting_duration, chronus.race_duration); } // exposing bettor info for DApp function get_bettor_nfo() external constant returns (uint, uint, bool) { bettor_info info = bettors[msg.sender]; return (info.bets_total, info.bets.length, info.rewarded); } // exposing bets info for DApp function get_bet_nfo(uint bet_num) external constant returns (uint, uint) { bettor_info info = bettors[msg.sender]; bet_info b_info = info.bets[bet_num]; return (b_info.dog, b_info.amount); } // =========== race lifecycle management functions ================================================================================ // place the oraclize queries and open betting function setup_race(uint betting_period, uint racing_period) public onlyOwner beforeBetting payable returns(bool) { // We have to send 2 queries for each dog; check if we have enough ether for this require (oraclizeLib.oraclize_getPrice("URL", 500000) * 2 * dogs_count < this.balance); chronus.starting_time = block.timestamp; chronus.betting_open = true; uint delay = betting_period.add(60); //slack time 1 minute chronus.betting_duration = delay; oraclize_query_ids[oraclizeLib.oraclize_query(delay, "URL", "json(https://api.coinmarketcap.com/v1/ticker/bitcoin/).0.price_usd", 500000)] = 1; oraclize_query_ids[oraclizeLib.oraclize_query(delay, "URL", "json(https://api.coinmarketcap.com/v1/ticker/ethereum/).0.price_usd", 500000)] = 2; oraclize_query_ids[oraclizeLib.oraclize_query(delay, "URL", "json(https://api.coinmarketcap.com/v1/ticker/litecoin/).0.price_usd", 500000)] = 3; oraclize_query_ids[oraclizeLib.oraclize_query(delay, "URL", "json(https://api.coinmarketcap.com/v1/ticker/bitcoin-cash/).0.price_usd", 500000)] = 4; oraclize_query_ids[oraclizeLib.oraclize_query(delay, "URL", "json(https://api.coinmarketcap.com/v1/ticker/ripple/).0.price_usd", 500000)] = 5; delay = delay.add(racing_period); oraclize_query_ids[oraclizeLib.oraclize_query(delay, "URL", "json(https://api.coinmarketcap.com/v1/ticker/bitcoin/).0.price_usd", 500000)] = 1; oraclize_query_ids[oraclizeLib.oraclize_query(delay, "URL", "json(https://api.coinmarketcap.com/v1/ticker/ethereum/).0.price_usd", 500000)] = 2; oraclize_query_ids[oraclizeLib.oraclize_query(delay, "URL", "json(https://api.coinmarketcap.com/v1/ticker/litecoin/).0.price_usd", 500000)] = 3; oraclize_query_ids[oraclizeLib.oraclize_query(delay, "URL", "json(https://api.coinmarketcap.com/v1/ticker/bitcoin-cash/).0.price_usd", 500000)] = 4; oraclize_query_ids[oraclizeLib.oraclize_query(delay, "URL", "json(https://api.coinmarketcap.com/v1/ticker/ripple/).0.price_usd", 500000)] = 5; OraclizeQuery("Oraclize queries were sent"); chronus.race_duration = delay; return true; } // end race and transfer house fee (called internally by callback) function end_race() internal { chronus.race_end = true; // calculate winning pool(s) and their total amount for (uint dog = 0; dog < dogs_count; dog++) { if (pools[dog].delta == max_delta) { pools[dog].winner = true; winning_bets_total = winning_bets_total.add(pools[dog].bets_total); } } // calculate house fee and transfer it to contract owner uint house_fee; if (winning_bets_total == 0) { // No winners => house takes all the money reward_total = 0; house_fee = this.balance; } else { if (winning_bets_total == bets_total) { // All the bettors are winners => void the race => no house fee; everyone gets their bets back chronus.race_voided = true; house_fee = 0; } else { house_fee = bets_total.mul(house_fee_pct).div(100); // calculate house fee as % of total bets } reward_total = bets_total.sub(house_fee); // subtract house_fee from total reward house_fee = this.balance - reward_total; // this.balance will also include remains of kickcstart ether } owner.transfer(house_fee); } // in case of any errors in race, enable full refund for the bettors to claim function void_race() external onlyOwner { require(now > chronus.starting_time + chronus.race_duration); require((chronus.betting_open && !chronus.race_start) || (chronus.race_start && !chronus.race_end)); chronus.betting_open = false; chronus.race_voided = true; chronus.race_end = true; } // method to retrieve unclaimed winnings after claim period has ended function recover_unclaimed_bets() external onlyOwner { require(now > chronus.starting_time + chronus.race_duration + claim_period); require(chronus.race_end); owner.transfer(this.balance); } // selfdestruct (returns balance to the owner) function kill() external onlyOwner { selfdestruct(msg.sender); } }
0.4.18