├── contracts ├── SafeMath.sol ├── EOSBetDice.sol ├── EOSBetBankroll.sol ├── EOSBetSlots.sol └── usingOraclize.sol └── README.md /contracts/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | 4 | /** 5 | * @title SafeMath 6 | * @dev Math operations with safety checks that throw on error 7 | */ 8 | library SafeMath { 9 | 10 | /** 11 | * @dev Multiplies two numbers, throws on overflow. 12 | */ 13 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 14 | if (a == 0) { 15 | return 0; 16 | } 17 | uint256 c = a * b; 18 | assert(c / a == b); 19 | return c; 20 | } 21 | 22 | /** 23 | * @dev Integer division of two numbers, truncating the quotient. 24 | */ 25 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 26 | // assert(b > 0); // Solidity automatically throws when dividing by 0 27 | uint256 c = a / b; 28 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 29 | return c; 30 | } 31 | 32 | /** 33 | * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). 34 | */ 35 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 36 | assert(b <= a); 37 | return a - b; 38 | } 39 | 40 | /** 41 | * @dev Adds two numbers, throws on overflow. 42 | */ 43 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 44 | uint256 c = a + b; 45 | assert(c >= a); 46 | return c; 47 | } 48 | } -------------------------------------------------------------------------------- /contracts/EOSBetDice.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import "./usingOraclize.sol"; 4 | import "./EOSBetBankroll.sol"; 5 | import "./SafeMath.sol"; 6 | 7 | contract EOSBetDice is usingOraclize, EOSBetGameInterface { 8 | 9 | using SafeMath for *; 10 | 11 | // events 12 | event BuyRolls(bytes32 indexed oraclizeQueryId); 13 | event LedgerProofFailed(bytes32 indexed oraclizeQueryId); 14 | event Refund(bytes32 indexed oraclizeQueryId, uint256 amount); 15 | event DiceSmallBet(uint16 actualRolls, uint256 data1, uint256 data2, uint256 data3, uint256 data4); 16 | event DiceLargeBet(bytes32 indexed oraclizeQueryId, uint16 actualRolls, uint256 data1, uint256 data2, uint256 data3, uint256 data4); 17 | 18 | // game data structure 19 | struct DiceGameData { 20 | address player; 21 | bool paidOut; 22 | uint256 start; 23 | uint256 etherReceived; 24 | uint256 betPerRoll; 25 | uint16 rolls; 26 | uint8 rollUnder; 27 | } 28 | 29 | mapping (bytes32 => DiceGameData) public diceData; 30 | 31 | // ether in this contract can be in one of two locations: 32 | uint256 public LIABILITIES; 33 | uint256 public DEVELOPERSFUND; 34 | 35 | // counters for frontend statistics 36 | uint256 public AMOUNTWAGERED; 37 | uint256 public GAMESPLAYED; 38 | 39 | // togglable values 40 | uint256 public ORACLIZEQUERYMAXTIME; 41 | uint256 public MINBET_perROLL; 42 | uint256 public MINBET_perTX; 43 | uint256 public ORACLIZEGASPRICE; 44 | uint256 public INITIALGASFORORACLIZE; 45 | uint8 public HOUSEEDGE_inTHOUSANDTHPERCENTS; // 1 thousanthpercent == 1/1000, 46 | uint8 public MAXWIN_inTHOUSANDTHPERCENTS; // determines the maximum win a user may receive. 47 | 48 | // togglable functionality of contract 49 | bool public GAMEPAUSED; 50 | bool public REFUNDSACTIVE; 51 | 52 | // owner of contract 53 | address public OWNER; 54 | 55 | // bankroller address 56 | address public BANKROLLER; 57 | 58 | // constructor 59 | function EOSBetDice() public { 60 | // ledger proof is ALWAYS verified on-chain 61 | oraclize_setProof(proofType_Ledger); 62 | 63 | // gas prices for oraclize call back, can be changed 64 | oraclize_setCustomGasPrice(8000000000); 65 | ORACLIZEGASPRICE = 8000000000; 66 | INITIALGASFORORACLIZE = 300000; 67 | 68 | AMOUNTWAGERED = 0; 69 | GAMESPLAYED = 0; 70 | 71 | GAMEPAUSED = false; 72 | REFUNDSACTIVE = true; 73 | 74 | ORACLIZEQUERYMAXTIME = 6 hours; 75 | MINBET_perROLL = 20 finney; 76 | MINBET_perTX = 100 finney; 77 | HOUSEEDGE_inTHOUSANDTHPERCENTS = 5; // 5/1000 == 0.5% house edge 78 | MAXWIN_inTHOUSANDTHPERCENTS = 20; // 20/1000 == 2.0% of bankroll can be won in a single bet, will be lowered once there is more investors 79 | OWNER = msg.sender; 80 | } 81 | 82 | //////////////////////////////////// 83 | // INTERFACE CONTACT FUNCTIONS 84 | //////////////////////////////////// 85 | 86 | function payDevelopersFund(address developer) public { 87 | require(msg.sender == BANKROLLER); 88 | 89 | uint256 devFund = DEVELOPERSFUND; 90 | 91 | DEVELOPERSFUND = 0; 92 | 93 | developer.transfer(devFund); 94 | } 95 | 96 | // just a function to receive eth, only allow the bankroll to use this 97 | function receivePaymentForOraclize() payable public { 98 | require(msg.sender == BANKROLLER); 99 | } 100 | 101 | //////////////////////////////////// 102 | // VIEW FUNCTIONS 103 | //////////////////////////////////// 104 | 105 | function getMaxWin() public view returns(uint256){ 106 | return (SafeMath.mul(EOSBetBankrollInterface(BANKROLLER).getBankroll(), MAXWIN_inTHOUSANDTHPERCENTS) / 1000); 107 | } 108 | 109 | //////////////////////////////////// 110 | // OWNER ONLY FUNCTIONS 111 | //////////////////////////////////// 112 | 113 | // WARNING!!!!! Can only set this function once! 114 | function setBankrollerContractOnce(address bankrollAddress) public { 115 | // require that BANKROLLER address == 0 (address not set yet), and coming from owner. 116 | require(msg.sender == OWNER && BANKROLLER == address(0)); 117 | 118 | // check here to make sure that the bankroll contract is legitimate 119 | // just make sure that calling the bankroll contract getBankroll() returns non-zero 120 | 121 | require(EOSBetBankrollInterface(bankrollAddress).getBankroll() != 0); 122 | 123 | BANKROLLER = bankrollAddress; 124 | } 125 | 126 | function transferOwnership(address newOwner) public { 127 | require(msg.sender == OWNER); 128 | 129 | OWNER = newOwner; 130 | } 131 | 132 | function setOraclizeQueryMaxTime(uint256 newTime) public { 133 | require(msg.sender == OWNER); 134 | 135 | ORACLIZEQUERYMAXTIME = newTime; 136 | } 137 | 138 | // store the gas price as a storage variable for easy reference, 139 | // and then change the gas price using the proper oraclize function 140 | function setOraclizeQueryGasPrice(uint256 gasPrice) public { 141 | require(msg.sender == OWNER); 142 | 143 | ORACLIZEGASPRICE = gasPrice; 144 | oraclize_setCustomGasPrice(gasPrice); 145 | } 146 | 147 | // should be ~175,000 to save eth 148 | function setInitialGasForOraclize(uint256 gasAmt) public { 149 | require(msg.sender == OWNER); 150 | 151 | INITIALGASFORORACLIZE = gasAmt; 152 | } 153 | 154 | function setGamePaused(bool paused) public { 155 | require(msg.sender == OWNER); 156 | 157 | GAMEPAUSED = paused; 158 | } 159 | 160 | function setRefundsActive(bool active) public { 161 | require(msg.sender == OWNER); 162 | 163 | REFUNDSACTIVE = active; 164 | } 165 | 166 | function setHouseEdge(uint8 houseEdgeInThousandthPercents) public { 167 | // house edge cannot be set > 5%, can be set to zero for promotions 168 | require(msg.sender == OWNER && houseEdgeInThousandthPercents <= 50); 169 | 170 | HOUSEEDGE_inTHOUSANDTHPERCENTS = houseEdgeInThousandthPercents; 171 | } 172 | 173 | function setMinBetPerRoll(uint256 minBet) public { 174 | require(msg.sender == OWNER && minBet > 1000); 175 | 176 | MINBET_perROLL = minBet; 177 | } 178 | 179 | function setMinBetPerTx(uint256 minBet) public { 180 | require(msg.sender == OWNER && minBet > 1000); 181 | 182 | MINBET_perTX = minBet; 183 | } 184 | 185 | function setMaxWin(uint8 newMaxWinInThousandthPercents) public { 186 | // cannot set bet limit greater than 5% of total BANKROLL. 187 | require(msg.sender == OWNER && newMaxWinInThousandthPercents <= 50); 188 | 189 | MAXWIN_inTHOUSANDTHPERCENTS = newMaxWinInThousandthPercents; 190 | } 191 | 192 | // rescue tokens inadvertently sent to the contract address 193 | function ERC20Rescue(address tokenAddress, uint256 amtTokens) public { 194 | require (msg.sender == OWNER); 195 | 196 | ERC20(tokenAddress).transfer(msg.sender, amtTokens); 197 | } 198 | 199 | // require that the query time is too slow, bet has not been paid out, and either contract owner or player is calling this function. 200 | // this will only be used/can occur on queries that are forwarded to oraclize in the first place. All others will be paid out immediately. 201 | function refund(bytes32 oraclizeQueryId) public { 202 | // store data in memory for easy access. 203 | DiceGameData memory data = diceData[oraclizeQueryId]; 204 | 205 | require(block.timestamp - data.start >= ORACLIZEQUERYMAXTIME 206 | && (msg.sender == OWNER || msg.sender == data.player) 207 | && (!data.paidOut) 208 | && LIABILITIES >= data.etherReceived 209 | && data.etherReceived > 0 210 | && REFUNDSACTIVE); 211 | 212 | // set paidout == true, so users can't request more refunds, and a super delayed oraclize __callback will just get reverted 213 | diceData[oraclizeQueryId].paidOut = true; 214 | 215 | // subtract etherReceived because the bet is being refunded 216 | LIABILITIES = SafeMath.sub(LIABILITIES, data.etherReceived); 217 | 218 | // then transfer the original bet to the player. 219 | data.player.transfer(data.etherReceived); 220 | 221 | // finally, log an event saying that the refund has processed. 222 | emit Refund(oraclizeQueryId, data.etherReceived); 223 | } 224 | 225 | function play(uint256 betPerRoll, uint16 rolls, uint8 rollUnder) public payable { 226 | 227 | // store in memory for cheaper access 228 | uint256 minBetPerTx = MINBET_perTX; 229 | 230 | require(!GAMEPAUSED 231 | && betPerRoll * rolls >= minBetPerTx 232 | && msg.value >= minBetPerTx 233 | && betPerRoll >= MINBET_perROLL 234 | && rolls > 0 235 | && rolls <= 1024 236 | && betPerRoll <= msg.value 237 | && rollUnder > 1 238 | && rollUnder < 98 239 | // make sure that the player cannot win more than the max win (forget about house edge here) 240 | && (SafeMath.mul(betPerRoll, 100) / (rollUnder - 1)) <= getMaxWin()); 241 | 242 | // equation for gas to oraclize is: 243 | // gas = (some fixed gas amt) + 1005 * rolls 244 | 245 | uint256 gasToSend = INITIALGASFORORACLIZE + (uint256(1005) * rolls); 246 | 247 | EOSBetBankrollInterface(BANKROLLER).payOraclize(oraclize_getPrice('random', gasToSend)); 248 | 249 | // oraclize_newRandomDSQuery(delay in seconds, bytes of random data, gas for callback function) 250 | bytes32 oraclizeQueryId = oraclize_newRandomDSQuery(0, 30, gasToSend); 251 | 252 | diceData[oraclizeQueryId] = DiceGameData({ 253 | player : msg.sender, 254 | paidOut : false, 255 | start : block.timestamp, 256 | etherReceived : msg.value, 257 | betPerRoll : betPerRoll, 258 | rolls : rolls, 259 | rollUnder : rollUnder 260 | }); 261 | 262 | // add the sent value into liabilities. this should NOT go into the bankroll yet 263 | // and must be quarantined here to prevent timing attacks 264 | LIABILITIES = SafeMath.add(LIABILITIES, msg.value); 265 | 266 | // log an event 267 | emit BuyRolls(oraclizeQueryId); 268 | } 269 | 270 | // oraclize callback. 271 | // Basically do the instant bet resolution in the play(...) function above, but with the random data 272 | // that oraclize returns, instead of getting psuedo-randomness from block.blockhash 273 | function __callback(bytes32 _queryId, string _result, bytes _proof) public { 274 | 275 | DiceGameData memory data = diceData[_queryId]; 276 | // only need to check these, as all of the game based checks were already done in the play(...) function 277 | require(msg.sender == oraclize_cbAddress() 278 | && !data.paidOut 279 | && data.player != address(0) 280 | && LIABILITIES >= data.etherReceived); 281 | 282 | // if the proof has failed, immediately refund the player his original bet... 283 | if (oraclize_randomDS_proofVerify__returnCode(_queryId, _result, _proof) != 0){ 284 | 285 | if (REFUNDSACTIVE){ 286 | // set contract data 287 | diceData[_queryId].paidOut = true; 288 | 289 | // if the call fails, then subtract the original value sent from liabilites and amount wagered, and then send it back 290 | LIABILITIES = SafeMath.sub(LIABILITIES, data.etherReceived); 291 | 292 | // transfer the original bet 293 | data.player.transfer(data.etherReceived); 294 | 295 | // log the refund 296 | emit Refund(_queryId, data.etherReceived); 297 | } 298 | // log the ledger proof fail 299 | emit LedgerProofFailed(_queryId); 300 | 301 | } 302 | // else, resolve the bet as normal with this miner-proof proven-randomness from oraclize. 303 | else { 304 | // save these in memory for cheap access 305 | uint8 houseEdgeInThousandthPercents = HOUSEEDGE_inTHOUSANDTHPERCENTS; 306 | 307 | // set the current balance available to the player as etherReceived 308 | uint256 etherAvailable = data.etherReceived; 309 | 310 | // logs for the frontend, as before... 311 | uint256[] memory logsData = new uint256[](4); 312 | 313 | // this loop is highly similar to the one from before. Instead of fully documented, the differences will be pointed out instead. 314 | uint256 winnings; 315 | uint16 gamesPlayed; 316 | 317 | // get this value outside of the loop for gas costs sake 318 | uint256 hypotheticalWinAmount = SafeMath.mul(SafeMath.mul(data.betPerRoll, 100), (1000 - houseEdgeInThousandthPercents)) / (data.rollUnder - 1) / 1000; 319 | 320 | while (gamesPlayed < data.rolls && etherAvailable >= data.betPerRoll){ 321 | 322 | // now, this roll is keccak256(_result, nonce) + 1 ... this is the main difference from using oraclize. 323 | 324 | if (uint8(uint256(keccak256(_result, gamesPlayed)) % 100) + 1 < data.rollUnder){ 325 | 326 | // now, just get the respective fields from data.field unlike before where they were in seperate variables. 327 | winnings = hypotheticalWinAmount; 328 | 329 | // assemble logs... 330 | if (gamesPlayed <= 255){ 331 | logsData[0] += uint256(2) ** (255 - gamesPlayed); 332 | } 333 | else if (gamesPlayed <= 511){ 334 | logsData[1] += uint256(2) ** (511 - gamesPlayed); 335 | } 336 | else if (gamesPlayed <= 767){ 337 | logsData[2] += uint256(2) ** (767 - gamesPlayed); 338 | } 339 | else { 340 | logsData[3] += uint256(2) ** (1023 - gamesPlayed); 341 | } 342 | } 343 | else { 344 | // leave 1 wei as a consolation prize :) 345 | winnings = 1; 346 | } 347 | gamesPlayed++; 348 | 349 | etherAvailable = SafeMath.sub(SafeMath.add(etherAvailable, winnings), data.betPerRoll); 350 | } 351 | 352 | // track that these games were played 353 | GAMESPLAYED += gamesPlayed; 354 | 355 | // and add the amount wagered 356 | AMOUNTWAGERED = SafeMath.add(AMOUNTWAGERED, SafeMath.mul(data.betPerRoll, gamesPlayed)); 357 | 358 | // IMPORTANT: we must change the "paidOut" to TRUE here to prevent reentrancy/other nasty effects. 359 | // this was not needed with the previous loop/code block, and is used because variables must be written into storage 360 | diceData[_queryId].paidOut = true; 361 | 362 | // decrease LIABILITIES when the spins are made 363 | LIABILITIES = SafeMath.sub(LIABILITIES, data.etherReceived); 364 | 365 | // get the developers cut, and send the rest of the ether received to the bankroller contract 366 | uint256 developersCut = SafeMath.mul(SafeMath.mul(data.betPerRoll, houseEdgeInThousandthPercents), gamesPlayed) / 5000; 367 | 368 | // add the devs cut to the developers fund. 369 | DEVELOPERSFUND = SafeMath.add(DEVELOPERSFUND, developersCut); 370 | 371 | EOSBetBankrollInterface(BANKROLLER).receiveEtherFromGameAddress.value(SafeMath.sub(data.etherReceived, developersCut))(); 372 | 373 | // force the bankroller contract to pay out the player 374 | EOSBetBankrollInterface(BANKROLLER).payEtherToWinner(etherAvailable, data.player); 375 | 376 | // log an event, now with the oraclize query id 377 | emit DiceLargeBet(_queryId, gamesPlayed, logsData[0], logsData[1], logsData[2], logsData[3]); 378 | } 379 | } 380 | 381 | // END OF CONTRACT. REPORT ANY BUGS TO DEVELOPMENT@EOSBET.IO 382 | // YES! WE _DO_ HAVE A BUG BOUNTY PROGRAM! 383 | 384 | // THANK YOU FOR READING THIS CONTRACT, HAVE A NICE DAY :) 385 | 386 | } -------------------------------------------------------------------------------- /contracts/EOSBetBankroll.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import "./SafeMath.sol"; 4 | 5 | contract EOSBetGameInterface { 6 | uint256 public DEVELOPERSFUND; 7 | uint256 public LIABILITIES; 8 | function payDevelopersFund(address developer) public; 9 | function receivePaymentForOraclize() payable public; 10 | function getMaxWin() public view returns(uint256); 11 | } 12 | 13 | contract EOSBetBankrollInterface { 14 | function payEtherToWinner(uint256 amtEther, address winner) public; 15 | function receiveEtherFromGameAddress() payable public; 16 | function payOraclize(uint256 amountToPay) public; 17 | function getBankroll() public view returns(uint256); 18 | } 19 | 20 | contract ERC20 { 21 | function totalSupply() constant public returns (uint supply); 22 | function balanceOf(address _owner) constant public returns (uint balance); 23 | function transfer(address _to, uint _value) public returns (bool success); 24 | function transferFrom(address _from, address _to, uint _value) public returns (bool success); 25 | function approve(address _spender, uint _value) public returns (bool success); 26 | function allowance(address _owner, address _spender) constant public returns (uint remaining); 27 | event Transfer(address indexed _from, address indexed _to, uint _value); 28 | event Approval(address indexed _owner, address indexed _spender, uint _value); 29 | } 30 | 31 | contract EOSBetBankroll is ERC20, EOSBetBankrollInterface { 32 | 33 | using SafeMath for *; 34 | 35 | // constants for EOSBet Bankroll 36 | 37 | address public OWNER; 38 | uint256 public MAXIMUMINVESTMENTSALLOWED; 39 | uint256 public WAITTIMEUNTILWITHDRAWORTRANSFER; 40 | uint256 public DEVELOPERSFUND; 41 | 42 | // this will be initialized as the trusted game addresses which will forward their ether 43 | // to the bankroll contract, and when players win, they will request the bankroll contract 44 | // to send these players their winnings. 45 | // Feel free to audit these contracts on etherscan... 46 | mapping(address => bool) public TRUSTEDADDRESSES; 47 | 48 | address public DICE; 49 | address public SLOTS; 50 | 51 | // mapping to log the last time a user contributed to the bankroll 52 | mapping(address => uint256) contributionTime; 53 | 54 | // constants for ERC20 standard 55 | string public constant name = "EOSBet Stake Tokens"; 56 | string public constant symbol = "EOSBETST"; 57 | uint8 public constant decimals = 18; 58 | // variable total supply 59 | uint256 public totalSupply; 60 | 61 | // mapping to store tokens 62 | mapping(address => uint256) public balances; 63 | mapping(address => mapping(address => uint256)) public allowed; 64 | 65 | // events 66 | event FundBankroll(address contributor, uint256 etherContributed, uint256 tokensReceived); 67 | event CashOut(address contributor, uint256 etherWithdrawn, uint256 tokensCashedIn); 68 | event FailedSend(address sendTo, uint256 amt); 69 | 70 | // checks that an address is a "trusted address of a legitimate EOSBet game" 71 | modifier addressInTrustedAddresses(address thisAddress){ 72 | 73 | require(TRUSTEDADDRESSES[thisAddress]); 74 | _; 75 | } 76 | 77 | // initialization function 78 | function EOSBetBankroll(address dice, address slots) public payable { 79 | // function is payable, owner of contract MUST "seed" contract with some ether, 80 | // so that the ratios are correct when tokens are being minted 81 | require (msg.value > 0); 82 | 83 | OWNER = msg.sender; 84 | 85 | // 100 tokens/ether is the inital seed amount, so: 86 | uint256 initialTokens = msg.value * 100; 87 | balances[msg.sender] = initialTokens; 88 | totalSupply = initialTokens; 89 | 90 | // log a mint tokens event 91 | emit Transfer(0x0, msg.sender, initialTokens); 92 | 93 | // insert given game addresses into the TRUSTEDADDRESSES mapping, and save the addresses as global variables 94 | TRUSTEDADDRESSES[dice] = true; 95 | TRUSTEDADDRESSES[slots] = true; 96 | 97 | DICE = dice; 98 | SLOTS = slots; 99 | 100 | WAITTIMEUNTILWITHDRAWORTRANSFER = 6 hours; 101 | MAXIMUMINVESTMENTSALLOWED = 500 ether; 102 | } 103 | 104 | /////////////////////////////////////////////// 105 | // VIEW FUNCTIONS 106 | /////////////////////////////////////////////// 107 | 108 | function checkWhenContributorCanTransferOrWithdraw(address bankrollerAddress) view public returns(uint256){ 109 | return contributionTime[bankrollerAddress]; 110 | } 111 | 112 | function getBankroll() view public returns(uint256){ 113 | // returns the total balance minus the developers fund, as the amount of active bankroll 114 | return SafeMath.sub(address(this).balance, DEVELOPERSFUND); 115 | } 116 | 117 | /////////////////////////////////////////////// 118 | // BANKROLL CONTRACT <-> GAME CONTRACTS functions 119 | /////////////////////////////////////////////// 120 | 121 | function payEtherToWinner(uint256 amtEther, address winner) public addressInTrustedAddresses(msg.sender){ 122 | // this function will get called by a game contract when someone wins a game 123 | // try to send, if it fails, then send the amount to the owner 124 | // note, this will only happen if someone is calling the betting functions with 125 | // a contract. They are clearly up to no good, so they can contact us to retreive 126 | // their ether 127 | // if the ether cannot be sent to us, the OWNER, that means we are up to no good, 128 | // and the ether will just be given to the bankrollers as if the player/owner lost 129 | 130 | if (! winner.send(amtEther)){ 131 | 132 | emit FailedSend(winner, amtEther); 133 | 134 | if (! OWNER.send(amtEther)){ 135 | 136 | emit FailedSend(OWNER, amtEther); 137 | } 138 | } 139 | } 140 | 141 | function receiveEtherFromGameAddress() payable public addressInTrustedAddresses(msg.sender){ 142 | // this function will get called from the game contracts when someone starts a game. 143 | } 144 | 145 | function payOraclize(uint256 amountToPay) public addressInTrustedAddresses(msg.sender){ 146 | // this function will get called when a game contract must pay payOraclize 147 | EOSBetGameInterface(msg.sender).receivePaymentForOraclize.value(amountToPay)(); 148 | } 149 | 150 | /////////////////////////////////////////////// 151 | // BANKROLL CONTRACT MAIN FUNCTIONS 152 | /////////////////////////////////////////////// 153 | 154 | 155 | // this function ADDS to the bankroll of EOSBet, and credits the bankroller a proportional 156 | // amount of tokens so they may withdraw their tokens later 157 | // also if there is only a limited amount of space left in the bankroll, a user can just send as much 158 | // ether as they want, because they will be able to contribute up to the maximum, and then get refunded the rest. 159 | function () public payable { 160 | 161 | // save in memory for cheap access. 162 | // this represents the total bankroll balance before the function was called. 163 | uint256 currentTotalBankroll = SafeMath.sub(getBankroll(), msg.value); 164 | uint256 maxInvestmentsAllowed = MAXIMUMINVESTMENTSALLOWED; 165 | 166 | require(currentTotalBankroll < maxInvestmentsAllowed && msg.value != 0); 167 | 168 | uint256 currentSupplyOfTokens = totalSupply; 169 | uint256 contributedEther; 170 | 171 | bool contributionTakesBankrollOverLimit; 172 | uint256 ifContributionTakesBankrollOverLimit_Refund; 173 | 174 | uint256 creditedTokens; 175 | 176 | if (SafeMath.add(currentTotalBankroll, msg.value) > maxInvestmentsAllowed){ 177 | // allow the bankroller to contribute up to the allowed amount of ether, and refund the rest. 178 | contributionTakesBankrollOverLimit = true; 179 | // set contributed ether as (MAXIMUMINVESTMENTSALLOWED - BANKROLL) 180 | contributedEther = SafeMath.sub(maxInvestmentsAllowed, currentTotalBankroll); 181 | // refund the rest of the ether, which is (original amount sent - (maximum amount allowed - bankroll)) 182 | ifContributionTakesBankrollOverLimit_Refund = SafeMath.sub(msg.value, contributedEther); 183 | } 184 | else { 185 | contributedEther = msg.value; 186 | } 187 | 188 | if (currentSupplyOfTokens != 0){ 189 | // determine the ratio of contribution versus total BANKROLL. 190 | creditedTokens = SafeMath.mul(contributedEther, currentSupplyOfTokens) / currentTotalBankroll; 191 | } 192 | else { 193 | // edge case where ALL money was cashed out from bankroll 194 | // so currentSupplyOfTokens == 0 195 | // currentTotalBankroll can == 0 or not, if someone mines/selfdestruct's to the contract 196 | // but either way, give all the bankroll to person who deposits ether 197 | creditedTokens = SafeMath.mul(contributedEther, 100); 198 | } 199 | 200 | // now update the total supply of tokens and bankroll amount 201 | totalSupply = SafeMath.add(currentSupplyOfTokens, creditedTokens); 202 | 203 | // now credit the user with his amount of contributed tokens 204 | balances[msg.sender] = SafeMath.add(balances[msg.sender], creditedTokens); 205 | 206 | // update his contribution time for stake time locking 207 | contributionTime[msg.sender] = block.timestamp; 208 | 209 | // now look if the attempted contribution would have taken the BANKROLL over the limit, 210 | // and if true, refund the excess ether. 211 | if (contributionTakesBankrollOverLimit){ 212 | msg.sender.transfer(ifContributionTakesBankrollOverLimit_Refund); 213 | } 214 | 215 | // log an event about funding bankroll 216 | emit FundBankroll(msg.sender, contributedEther, creditedTokens); 217 | 218 | // log a mint tokens event 219 | emit Transfer(0x0, msg.sender, creditedTokens); 220 | } 221 | 222 | function cashoutEOSBetStakeTokens(uint256 _amountTokens) public { 223 | // In effect, this function is the OPPOSITE of the un-named payable function above^^^ 224 | // this allows bankrollers to "cash out" at any time, and receive the ether that they contributed, PLUS 225 | // a proportion of any ether that was earned by the smart contact when their ether was "staking", However 226 | // this works in reverse as well. Any net losses of the smart contract will be absorbed by the player in like manner. 227 | // Of course, due to the constant house edge, a bankroller that leaves their ether in the contract long enough 228 | // is effectively guaranteed to withdraw more ether than they originally "staked" 229 | 230 | // save in memory for cheap access. 231 | uint256 tokenBalance = balances[msg.sender]; 232 | // verify that the contributor has enough tokens to cash out this many, and has waited the required time. 233 | require(_amountTokens <= tokenBalance 234 | && contributionTime[msg.sender] + WAITTIMEUNTILWITHDRAWORTRANSFER <= block.timestamp 235 | && _amountTokens > 0); 236 | 237 | // save in memory for cheap access. 238 | // again, represents the total balance of the contract before the function was called. 239 | uint256 currentTotalBankroll = getBankroll(); 240 | uint256 currentSupplyOfTokens = totalSupply; 241 | 242 | // calculate the token withdraw ratio based on current supply 243 | uint256 withdrawEther = SafeMath.mul(_amountTokens, currentTotalBankroll) / currentSupplyOfTokens; 244 | 245 | // developers take 1% of withdrawls 246 | uint256 developersCut = withdrawEther / 100; 247 | uint256 contributorAmount = SafeMath.sub(withdrawEther, developersCut); 248 | 249 | // now update the total supply of tokens by subtracting the tokens that are being "cashed in" 250 | totalSupply = SafeMath.sub(currentSupplyOfTokens, _amountTokens); 251 | 252 | // and update the users supply of tokens 253 | balances[msg.sender] = SafeMath.sub(tokenBalance, _amountTokens); 254 | 255 | // update the developers fund based on this calculated amount 256 | DEVELOPERSFUND = SafeMath.add(DEVELOPERSFUND, developersCut); 257 | 258 | // lastly, transfer the ether back to the bankroller. Thanks for your contribution! 259 | msg.sender.transfer(contributorAmount); 260 | 261 | // log an event about cashout 262 | emit CashOut(msg.sender, contributorAmount, _amountTokens); 263 | 264 | // log a destroy tokens event 265 | emit Transfer(msg.sender, 0x0, _amountTokens); 266 | } 267 | 268 | // TO CALL THIS FUNCTION EASILY, SEND A 0 ETHER TRANSACTION TO THIS CONTRACT WITH EXTRA DATA: 0x7a09588b 269 | function cashoutEOSBetStakeTokens_ALL() public { 270 | 271 | // just forward to cashoutEOSBetStakeTokens with input as the senders entire balance 272 | cashoutEOSBetStakeTokens(balances[msg.sender]); 273 | } 274 | 275 | //////////////////// 276 | // OWNER FUNCTIONS: 277 | //////////////////// 278 | // Please, be aware that the owner ONLY can change: 279 | // 1. The owner can increase or decrease the target amount for a game. They can then call the updater function to give/receive the ether from the game. 280 | // 1. The wait time until a user can withdraw or transfer their tokens after purchase through the default function above ^^^ 281 | // 2. The owner can change the maximum amount of investments allowed. This allows for early contributors to guarantee 282 | // a certain percentage of the bankroll so that their stake cannot be diluted immediately. However, be aware that the 283 | // maximum amount of investments allowed will be raised over time. This will allow for higher bets by gamblers, resulting 284 | // in higher dividends for the bankrollers 285 | // 3. The owner can freeze payouts to bettors. This will be used in case of an emergency, and the contract will reject all 286 | // new bets as well. This does not mean that bettors will lose their money without recompense. They will be allowed to call the 287 | // "refund" function in the respective game smart contract once payouts are un-frozen. 288 | // 4. Finally, the owner can modify and withdraw the developers reward, which will fund future development, including new games, a sexier frontend, 289 | // and TRUE DAO governance so that onlyOwner functions don't have to exist anymore ;) and in order to effectively react to changes 290 | // in the market (lower the percentage because of increased competition in the blockchain casino space, etc.) 291 | 292 | function transferOwnership(address newOwner) public { 293 | require(msg.sender == OWNER); 294 | 295 | OWNER = newOwner; 296 | } 297 | 298 | function changeWaitTimeUntilWithdrawOrTransfer(uint256 waitTime) public { 299 | // waitTime MUST be less than or equal to 10 weeks 300 | require (msg.sender == OWNER && waitTime <= 6048000); 301 | 302 | WAITTIMEUNTILWITHDRAWORTRANSFER = waitTime; 303 | } 304 | 305 | function changeMaximumInvestmentsAllowed(uint256 maxAmount) public { 306 | require(msg.sender == OWNER); 307 | 308 | MAXIMUMINVESTMENTSALLOWED = maxAmount; 309 | } 310 | 311 | 312 | function withdrawDevelopersFund(address receiver) public { 313 | require(msg.sender == OWNER); 314 | 315 | // first get developers fund from each game 316 | EOSBetGameInterface(DICE).payDevelopersFund(receiver); 317 | EOSBetGameInterface(SLOTS).payDevelopersFund(receiver); 318 | 319 | // now send the developers fund from the main contract. 320 | uint256 developersFund = DEVELOPERSFUND; 321 | 322 | // set developers fund to zero 323 | DEVELOPERSFUND = 0; 324 | 325 | // transfer this amount to the owner! 326 | receiver.transfer(developersFund); 327 | } 328 | 329 | // rescue tokens inadvertently sent to the contract address 330 | function ERC20Rescue(address tokenAddress, uint256 amtTokens) public { 331 | require (msg.sender == OWNER); 332 | 333 | ERC20(tokenAddress).transfer(msg.sender, amtTokens); 334 | } 335 | 336 | /////////////////////////////// 337 | // BASIC ERC20 TOKEN OPERATIONS 338 | /////////////////////////////// 339 | 340 | function totalSupply() constant public returns(uint){ 341 | return totalSupply; 342 | } 343 | 344 | function balanceOf(address _owner) constant public returns(uint){ 345 | return balances[_owner]; 346 | } 347 | 348 | // don't allow transfers before the required wait-time 349 | // and don't allow transfers to this contract addr, it'll just kill tokens 350 | function transfer(address _to, uint256 _value) public returns (bool success){ 351 | require(balances[msg.sender] >= _value 352 | && contributionTime[msg.sender] + WAITTIMEUNTILWITHDRAWORTRANSFER <= block.timestamp 353 | && _to != address(this) 354 | && _to != address(0)); 355 | 356 | // safely subtract 357 | balances[msg.sender] = SafeMath.sub(balances[msg.sender], _value); 358 | balances[_to] = SafeMath.add(balances[_to], _value); 359 | 360 | // log event 361 | emit Transfer(msg.sender, _to, _value); 362 | return true; 363 | } 364 | 365 | // don't allow transfers before the required wait-time 366 | // and don't allow transfers to the contract addr, it'll just kill tokens 367 | function transferFrom(address _from, address _to, uint _value) public returns(bool){ 368 | require(allowed[_from][msg.sender] >= _value 369 | && balances[_from] >= _value 370 | && contributionTime[_from] + WAITTIMEUNTILWITHDRAWORTRANSFER <= block.timestamp 371 | && _to != address(this) 372 | && _to != address(0)); 373 | 374 | // safely add to _to and subtract from _from, and subtract from allowed balances. 375 | balances[_to] = SafeMath.add(balances[_to], _value); 376 | balances[_from] = SafeMath.sub(balances[_from], _value); 377 | allowed[_from][msg.sender] = SafeMath.sub(allowed[_from][msg.sender], _value); 378 | 379 | // log event 380 | emit Transfer(_from, _to, _value); 381 | return true; 382 | 383 | } 384 | 385 | function approve(address _spender, uint _value) public returns(bool){ 386 | 387 | allowed[msg.sender][_spender] = _value; 388 | emit Approval(msg.sender, _spender, _value); 389 | // log event 390 | return true; 391 | } 392 | 393 | function allowance(address _owner, address _spender) constant public returns(uint){ 394 | return allowed[_owner][_spender]; 395 | } 396 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WELCOME TO EOSBet.IO!!! 2 | 3 | ## This is the directory for our Ethereum Gambling contracts. 4 | 5 | #### Here is a quick description of our gambling system: 6 | 7 | Our contract system is fairly simple. There are 3 main contracts, `EOSBetBankroll.sol`, `EOSBetSlots.sol`, and `EOSBetDice.sol`. 8 | 9 | The bankroll contract is in charge of holding all the funds for the games. 10 | 11 | The games are in charge of taking bets, and interacting with the bankroll contract to payout players, and to receive the players losses. 12 | 13 | A problem with current on-chain gambling offerings, is the fact that Ethereum transactions are pretty slow, and unfortunately there's no way of getting around slow transacions. However, we allow bettors to "pre-purchase" up to 224 spins for slots, and 1024 rolls for dice. They then just have to wait for 1 transaction to resolve (+ a transaction from Oraclize), and then get to use our pretty front end (work-in-progress) to resolve the bets, and have fun gambling at their own pace! 14 | 15 | Again, the bankroll is used to payout players, and pay for any oraclize fees that will be charged to the Dice or Slots contract. 16 | 17 | Also, the bankroll can be used by **ANYONE** to deposit money, and *stake* our games with Ether, receiving dividends from the small house edge that is in our games. Depositing money will result in receiving EOSBetStakeTokens in proportion to how much money a user deposited into the bankroll. There is a variable amount of time `WAITTIMEUNTILWITHDRAWORTRANSFER` that their Ether must be "staked" for, and then they are free to `cashoutEOSBetStakeTokens()` with the amount of tokens that they wish to cashout. The amount of tokens they wish to cash out are compared with the "global" supply of tokens, which then is used to determine what proportion of Ether they own, which is then sent to them. 18 | 19 | The EOSBetStakeTokens are *soley* accounting tokens, and don't have any value outside of our contracts. However, since ERC20 tokens are clearly all the rage right now, we decided to make EOSBetStakeTokens ERC20 compatible, because why not? :) 20 | 21 | Also, note that 20% of the profit of the games, and 1% of withdrawals from the bankroll will be sent to the `DEVELOPERSFUND` so that we can fund marketing, more games, and etc. 22 | 23 | Finally, note that everything is supposed to be trustless in the game contracts. Bankrollers should be able to deposit and stake the games trustlessly, and gamblers should be able to play a fair game, also trustlessly. Besides general functionality like pausing the games, changing some parameters, the developers shouldn't have much power in these contracts. **NOTE** that we cannot add new games! This is purposeful, because we could just add a game called `DeveloperAlwaysWins.sol` and then drain the bankroll. That wouldn't be fun. 24 | 25 | Yes, we will take out the `emergencySelfDestruct()` function when entering production. No, this is not a bug that can be caught in our bug bounty program. 26 | 27 | 28 | #### Now lets dive a little deeper into our contracts: 29 | 30 | ##### EOSBetDice.sol & EOSBetSlots.sol 31 | 32 | All games must follow the `contract EOSBetGameInterface{}` interface contract. This is as follows: 33 | 34 | ``` 35 | contract EOSBetGameInterface { 36 | uint256 public DEVELOPERSFUND; 37 | uint256 public LIABILITIES; 38 | function payDevelopersFund(address developer) public; 39 | function receivePaymentForOraclize() payable public; 40 | function getMaxWin() public view returns(uint256); 41 | } 42 | ``` 43 | 44 | First, we have the `DEVELOPERSFUND`. 20% of the game profit will increment this fund. This is taken by `betSize * numberOfBets * houseEdge * 20%`. 45 | 46 | Second, we have the `LIABILITIES`. All large bets will initially increment the liabilities when forwarding the randomness call to oraclize.it. When oraclize calls back, `LIABILITIES` will decrement, and the bet is treated like a normal bet. 47 | 48 | `payDevelopersFund(address developer)` can only be called by the BANKROLLER contract, which can only be called by OWNER. This just sends the developers fund to a specified address, and then sets the fund equal to zero. 49 | 50 | `receivePaymentForOraclize() payable` is in charge of receiving ether from the bankroll, equivalent to the fee + gas * gasPrice that oraclize is charging. 51 | 52 | `getMaxWin() view` is a function that asks the BANKROLLER contract to see how much investment there is in the bankroll, and then determines the maximum win size, that a bettor can win. We don't want to accept too large of bets and go bankrupt!! 53 | 54 | ##### EOSBetDice.sol 55 | 56 | We start off with a bunch of owner only functions to change parameters to the games. Dangerous parameters have a cap on them, that even the owner cannot go above when he is calling the functions. Pretty straightforward... 57 | 58 | Again, we have a suicide/selfdestruct function, this will be taken out for production, and is only for testing! 59 | 60 | `refund(bytes32 oraclizeQueryId)` is meant to be called by either the player or the owner, if the oraclize query is taking too long to `__callback`. This can be a (rare) problem with oraclize, and we want people to be able to retreive their funds if oraclize is slow/fails to callback. 61 | 62 | `play(uint256 betPerRoll, uint16 rolls, uint8 rollUnder) payable` is the main function in this contract. Players are allowed to specify the amount of bet for each roll of dice, the total number of rolls, and the number that they are trying to roll under. (Ex: a choice of 77 means that 1-76 are the winning numbers, and 77-100 are losers!) 63 | 64 | The bet infomation will then be saved (`SSTORE`) and then oraclize will be called to supply randomness, to resolve the bet safely. 65 | 66 | Once oraclize calls the `__callback()` function the bet will start to resolve. We check the oraclize ledger proof to return `0` as required. If the oraclize proof fails, then we just refund the player immediately. Clearly, there is an issue where oraclize could chose to only (correcly) `__callback` if they are going to win, and just make the ledger proof fail if they lose. However, other gambling contracts put vastly more trust into oraclize than we do (where they could just outright win every single game) so we have decided to give them the benefit of the doubt here. 67 | 68 | **However**, if we feel that oraclize is cheating (will be easily detectable), then we can just toggle off `REFUNDSACTIVE`. We would like to keep them on, of course, so that our players have a better experience. :) 69 | 70 | A while loop starts, `while (gamesPlayed < rolls && etherAvailable >= betPerRoll)` where either every single roll will resolve as specified by the player, *or* the player will go bankrupt. 71 | 72 | A winning roll will credit based on the roll under number according to the standard dice formula: `hypotheticalWinAmount = SafeMath.mul(SafeMath.mul(betPerRoll, 100), (1000 - houseEdgeInThousandthPercents)) / (rollUnder - 1) / 1000;` 73 | 74 | Once either all rolls resolve, or the player doesn't have the funds to play another roll, then the bankroll will receive the initial captial used to bet (subtracting from `LIABILITIES` if this is in the `__callback`), and the bankroll will send any winnings to the player. The wins are sent **SAFELY** by using msg.sender.send(amt), and catching failures. If a .send() fails, then the amount gets sent to the OWNER instead. We could have implemented some sort of fund recovery fairly easily, but decided to just not allow external contracts (with excessive logic in `function () payable`) to call our contracts. If you MUST call our gaming contracts with an external contract with a bunch of fallback logic, you are clearly doing something sketchy, and can contact us to recover your funds :) 75 | 76 | *Note that if the `OWNER` send fails, then the funds will be given to our bankroll investors, because we change the `OWNER` address to be a contract, then we are also doing something sketchy.* 77 | 78 | Lastly, we must log some events so that the frontend knows what happened to the dice game. Logs can get expensive, so we did this the cheapest way. The frontend really just needs to know whether the player one or lost a game, so we can represent each game result using binary. (Ex: 10011 = win, loss, loss, win, win). 79 | 80 | ##### EOSBetSlots.sol 81 | 82 | Slots is a *very* similar contract, of course, and just about everything that was said about dice, also applies to slots. However, the gameplay is obviously different, and more complex. 83 | 84 | Here, the `play(uint8 credits) payable` function only takes 1 argument, `credits`, and the amount bet per credit will just be `msg.value / credits`. Again, we do the necessary checks, and forward the bet to oraclize. 85 | 86 | There are 64 positions on the slots dial, and given the combinations, and their payouts, we have a 4.9% house edge, which is **very** generous. The standard casino does something between 15%-25% on slots, which is absolutely brutal. 87 | 88 | There we loop though all the credits, find the payout given each combination, and then log the events for the front end. The logs are minimalistic, again, because they can get costly. Since there are 7 different items on the dial, we can store each dial in a uint4. The frontend then parses these hex-encoded logs into octal notation, and then gets the dial locations and replays the spins to our bettors! (Please stay tuned for the actual games, another 2-3 weeks, we promise!) 89 | 90 | **positions for slots** 91 | ``` 92 | // STOPS REEL#1 REEL#2 REEL#3 93 | /////////////////////////////////////////// 94 | // gold ether 0 // 1 // 3 // 1 // 95 | // silver ether 1 // 7 // 1 // 6 // 96 | // bronze ether 2 // 1 // 7 // 6 // 97 | // gold planet 3 // 5 // 7 // 6 // 98 | // silverplanet 4 // 9 // 6 // 7 // 99 | // bronzeplanet 5 // 9 // 8 // 6 // 100 | // ---blank--- 6 // 32 // 32 // 32 // 101 | /////////////////////////////////////////// 102 | ``` 103 | 104 | **payouts for slots** 105 | ``` 106 | // LANDS ON // PAYS // 107 | //////////////////////////////////////////////// 108 | // Bronze E -> Silver E -> Gold E // 5000 // 109 | // 3x Gold Ether // 1777 // 110 | // 3x Silver Ether // 250 // 111 | // 3x Bronze Ether // 250 // 112 | // 3x any Ether // 95 // 113 | // Bronze P -> Silver P -> Gold P // 90 // 114 | // 3x Gold Planet // 50 // 115 | // 3x Silver Planet // 25 // 116 | // Any Gold P & Silver P & Bronze P // 20 // 117 | // 3x Bronze Planet // 10 // 118 | // Any 3 planet type // 3 // 119 | // Any 3 gold // 3 // 120 | // Any 3 silver // 2 // 121 | // Any 3 bronze // 2 // 122 | // Blank, blank, blank // 1 // 123 | // else // 0 // 124 | //////////////////////////////////////////////// 125 | ``` 126 | 127 | There are found in our code too, for easy reference. 128 | 129 | ##### EOSBetBankroll.sol 130 | 131 | The bankroll is in charge of all payouts, like the cashier at a casino. 132 | 133 | We have `payEtherToWinner(uint256 amtEther, address winner)` which safely sends ether to the winner. Again, we do not support calling our contracts with external contracts (with a lot of `function () playable` logic), so if your send fails, you will have to retreive the money from us, the OWNER. The function can only be called by a game contract, that get specified at initialization. 134 | 135 | We have `receiveEtherFromGameAddress() payable` which is the function that the game contracts call when sending the bet ether to the bankroll. The function can only be called by a game contract, that get specified at initialization. 136 | 137 | `payOraclize(uint256 amountToPay)` will send any amount to a game contract that calls this function. The game contracts calculate how much ether is going to be needed from oraclize, and then call this function to get the money from the BANKROLLER. The function can only be called by a game contract, whose addresses get specified at initialization. 138 | 139 | Our fallback function `function () public payable` is the way that users can stake our bankroll. They can send any amount of ether to our contract, and receive a proportional amount of accounting tokens. There is a cap on the amount that can be contributed, denoted by `MAXIMUMINVESTMENTSALLOWED` however this can be raised and lowered by the OWNER. If our contracts catch fire, we might want to limit deposits, so it doesn't get too out of hand ;) 140 | 141 | Extra ether, that is deposited over `MAXIMUMINVESTMENTSALLOWED` will be refunded to the investor in the same function call. 142 | 143 | Players can cash out using `cashoutEOSBetStakeTokens(uint256 _amountTokens)`. They just specify the amount of tokens they want to cash out, these tokens are burned and removed from total supply, and then they will receive a proportional amount of ether. Hopefully, if our games do well, they will receive some extra ether due to the house edge. If we have a lot of bettors, we can see being a bankroll backed will be quite lucrative, but no promises! 144 | 145 | `cashoutEOSBetStakeTokens_ALL()` just fully divests a player from the bankroll. This is nice because users can just send 0 ether to our contract, with extra data `0x7a09588b` to fully divest right away. They don't need to use our frontend at all, and can just MEW with a hardware wallet for safety! Still waiting on that Ledger <-> MetaMask integration... ;) 146 | 147 | Note that there is a required stake time that users must leave their funds locked into the bankroll for. This will intially be only a few hours, but could get up to 10 weeks maximum (unlikely). Also, 1% of withdrawals will be given to developers for marketing reasons! 148 | 149 | `withdrawDevelopersFund(address receiver)` tells DICE and SLOTS to give their `DEVELOPERSFUND` to a specified address. It also has the bankroll give any accumulated `DEVELOPERSFUND` to the same address, and then zeros out all these counters on all the contracts. 150 | 151 | After this, is just basic ERC20 functionality. We do not allow users to transfer tokens until their locktime is up, or it would be too difficult to keep track of which tokens are not allowed to be cashed out. We also do not allow transfers to address(this) because it will just burn tokens. 152 | 153 | ###### usingOraclize.sol 154 | 155 | This is the standard oraclize contract. If there are issues you find, definitely bring it up with the oraclize team, I'm sure they have a much larger bounty than I could afford! 156 | 157 | ##### SafeMath.sol 158 | 159 | This is standard Zeppelin SafeMath, again, issues should be brought up with the Zeppelin team! 160 | 161 | ##### END 162 | 163 | Please submit any issues to our GitHub, and feel free to contact development@EOSBet.IO with any issues. 164 | 165 | If you are privacy centric, please use our PGP Key below. 166 | 167 | Cheers! 168 | Team EOSBet 169 | 170 | 171 | ``` 172 | -----BEGIN PGP PUBLIC KEY BLOCK----- 173 | 174 | mQINBFq6tpkBEAC+tVyf6hXMfQ5oQQn+iU5XRj4Gt7QOCVNhNtCdKcZlHEQ21wmB 175 | qvrhGPS4DPUZUbtGqv9VL/Y/Jm8/GqcuDROAgEL4QBr1p6EL8K+c1QbMv2hGGnam 176 | oin+8gvrVRfHWma8d///f/IP5w9Ry+QDLGXzOFWd6FrJJkUJCRpH2TYBZaiaTjf9 177 | J1zTHpVGtl/l3UyytZX1RV91tEeNNp1zhqklL6eOi3SGX4819Y/tzbGZ7UqyT7TL 178 | fvaNG/8PFVbvcimW+hIdQJSxm79ioCgu76X8oAfVRlui6Gxwq3jj3aS+Cm4ali6p 179 | hnt/tWnm9YsiG64zX48gn6fuAQvC4OLMKgLL30OuhxzYD44eL4oo/fCv+J9zYjQf 180 | nSAB5wXv8Q8aEECscekTJHtjlxoSXokRHqD86pmmGaBLXg/5ENCaF+zDR/Amzowz 181 | +1CvhDnsFaqj3cuvT7aZp4SV20jExvn2nYaHX8Aq/GbHb+hnPxDvdwQACQY4xcm/ 182 | ux1mpvL8VXyy36Q/sIlZLLycPhUwLqqqubd+T8slEnLbPQhQUyJ6qx6qRAOk/Op5 183 | KzsBs5KW/UKWHae48ABXaKr6J5VML0jv1O+LGFS8mYyQWanNZScINw5js/o6YMAI 184 | hJZ3jc/eG+B1cgpDkfhFLv4H3C6AHF92M3ErHZhxPXjl4VAjdasI/1ii8wARAQAB 185 | tCFFT1NCZXQuSU8gPGRldmVsb3BtZW50QEVPU0JldC5JTz6JAlQEEwEIAD4WIQR/ 186 | Ttv+kOFvYOm3XPi4cemCH4J1pQUCWrq2mQIbAwUJB4YfgAULCQgHAgYVCAkKCwIE 187 | FgIDAQIeAQIXgAAKCRC4cemCH4J1pYTjD/95O9pMyMRaHAxpBBjxDkR0rAl+dkHZ 188 | 68PusIxuJyuq4pi7YK9nUoeu78TBCcioOnXVD/1u8zf9It/TpwldsT6kAFD77TuR 189 | U+pPfa9m7QxXEGfMb1yQEmtkK5Cn0w3lI2pWD3f0XXqwHciOS+z7pVdO2+2TmVDW 190 | WPc+sM3e5lJ2yRA3e4pO7pc08nKy1Y4PC4MbLBapzsdOg6wM0GyxtdjR43t/v4SC 191 | 5AgtLiQR1pLyKouRn5Oqcc9a9Uz8HtsSPjJQkZj24q+j+OUHXFrPAbzQ1UdM+Uat 192 | co6kRZYqKU6eq/tk6w2r9rpc5sAb5il1dxvXB78fFeRBt193TGTPh9sYJEaJ5I6P 193 | KAqTuQ3cfFffiw9z5XBfzQKLBe1KLCtkhfw0MqcGrQvHRVTESb2BhrYt+uspWpT8 194 | 24+akqEKrmjIrPPm3M0jqes+I6klD7IeVu0KpURfJ6ATZMF2IUDIpmT1nByNGVzq 195 | AxYeO/j+LfJyXnUHlkmEbpmcrFDcSdBG4n/ljwhJxZDpjoOb4LtkhW4acmPshmP7 196 | QKbJ3IyVWn4XEea8jGOo5Lan/uotbjsV1wiF23qWJOOUxZo9+5MxiKEJapdQaAaE 197 | YwiLini8r2xZB/qHwbpP0GtVYz5uwd6pCSKOV+gmvFTFj07OZNXdqxZLmSSxtyWI 198 | jxXGtZLuyTB+ArkCDQRauraZARAAxapwhyFTVybnpBs6RLEQce4TvofjMT6wL+7y 199 | +ekMpXEJKTuaMbHrvWDbhOdpM+1Zld/eDfUsbBlxLH4V1bOUMiqqto/wc+sEkrWV 200 | a8bIJv6oSs8VXDXonczRcQ+ZIvfv+p4IFxW8C0JAKmXgWEtSS95Ksbay6YUG3Qbk 201 | nsbAGovSdz4HWWjZO/FpAc6CJCcUJyhY5gPG1YlvwncSGJGpFz02xwI0K/+JfmH4 202 | sLvgD6hMyjYyQNkwtQvZc6osty9XcxG5Qb4Ujd7+mjaWxwkgXl+ElSnYu3kkamNZ 203 | UlRYFFVJTT87zRf7O35+PSb6rMN/h+XanF3lXIz0PLFOTxdOA6Ve4Jiokqv/PKJm 204 | eAX8+Cw1jqBVmmKYP1N6e8LOfRwM9505zE8g774bSQExVuGRMaAY2c14hKjRHebT 205 | L0n0udP52irZezwj/rnxuQqdGAJa5pDWHbrDQhx2AzleQso7c7eA/epXP9jkOlmJ 206 | ypGE3MzmCG10p2SCghGsMV7joo1wwwGfaJGrDeNzvjaePKyO4PwVM1ImzwM3+5uf 207 | qEvvXshrP2BUK1OoCBIyLlGOMFvLr3RoLxILLSSxOzQ+FmR6BfKMHX/1Swz/ak6o 208 | 3LVwF23PDiR25G41VPEmzIfba4s5r4Z3SxLdUXhlBTwYIN1osX3qe+6Hor+b8ajb 209 | ZN4/Yi8AEQEAAYkCPAQYAQgAJhYhBH9O2/6Q4W9g6bdc+Lhx6YIfgnWlBQJauraZ 210 | AhsMBQkHhh+AAAoJELhx6YIfgnWlJGkQAKi/q0YlL48gh8NaYF1eDb4hPLPcxb8C 211 | YF9oT2IAE82713aT9LtL3/KwviuUVXy1VHY6uCIn5gImqysAFsQIQCSH3KaPU4Be 212 | 2pDRyEGtlnqaaxXono4te3Ne+7Y/EIgdq5nJ3BgLH4dM0zgMHAiruCGN/FZmZ64Z 213 | mVoD/AhJsBlhDPh0eFd4Aha1FopR+G1NVZ0f8RbO6xFfMsf4Cduy62DzHluApskF 214 | j8F9eXLPWJ1RqBIBHdMNhv+nVMr4w6ae8ngC/3i5JFM0kpQSoBTHjTE6wSH44g5d 215 | FQdwACakJY59QNSGIsGyoMEJtYmiDjjyBq+vbldC3s+jHPoWAFEn+tWOaTbsTmUr 216 | qLuHIxph5LeV0xBsHAaxrj0/dVMgnS7ofTIIa7NlCbYhAucb6lhg+Td+kXdB4wp0 217 | ml2e4gZhssWY2oAUbdSeOTsRFs+e7FFxdZT8o9rTDN4chrCEqy4tTc840ZlhLt0F 218 | WfgKKmxf41GNdU98nCVlzSoMvYTi4kcJczjrUVr8uFIkf1yecylOAN8MaAyje/7n 219 | jsEWU9jXE9Pnnkh/0gd8acZWuFLrCdMUuSk+N67XpikIj2RxoLrMrAb14+usE6OM 220 | O8qL8s2EZetm0+BW1SgVmIFHaQlCzJuXkK5ZHS1J8cUfOP4T2acMGjpvUPO4Lapn 221 | iBNTpi4g5EPO 222 | =/tgd 223 | -----END PGP PUBLIC KEY BLOCK----- 224 | 225 | ``` 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /contracts/EOSBetSlots.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import "./usingOraclize.sol"; 4 | import "./EOSBetBankroll.sol"; 5 | import "./SafeMath.sol"; 6 | 7 | contract EOSBetSlots is usingOraclize, EOSBetGameInterface { 8 | 9 | using SafeMath for *; 10 | 11 | // events 12 | event BuyCredits(bytes32 indexed oraclizeQueryId); 13 | event LedgerProofFailed(bytes32 indexed oraclizeQueryId); 14 | event Refund(bytes32 indexed oraclizeQueryId, uint256 amount); 15 | event SlotsLargeBet(bytes32 indexed oraclizeQueryId, uint256 data1, uint256 data2, uint256 data3, uint256 data4, uint256 data5, uint256 data6, uint256 data7, uint256 data8); 16 | event SlotsSmallBet(uint256 data1, uint256 data2, uint256 data3, uint256 data4, uint256 data5, uint256 data6, uint256 data7, uint256 data8); 17 | 18 | // slots game structure 19 | struct SlotsGameData { 20 | address player; 21 | bool paidOut; 22 | uint256 start; 23 | uint256 etherReceived; 24 | uint8 credits; 25 | } 26 | 27 | mapping (bytes32 => SlotsGameData) public slotsData; 28 | 29 | // ether in this contract can be in one of two locations: 30 | uint256 public LIABILITIES; 31 | uint256 public DEVELOPERSFUND; 32 | 33 | // counters for frontend statistics 34 | uint256 public AMOUNTWAGERED; 35 | uint256 public DIALSSPUN; 36 | 37 | // togglable values 38 | uint256 public ORACLIZEQUERYMAXTIME; 39 | uint256 public MINBET_perSPIN; 40 | uint256 public MINBET_perTX; 41 | uint256 public ORACLIZEGASPRICE; 42 | uint256 public INITIALGASFORORACLIZE; 43 | uint16 public MAXWIN_inTHOUSANDTHPERCENTS; 44 | 45 | // togglable functionality of contract 46 | bool public GAMEPAUSED; 47 | bool public REFUNDSACTIVE; 48 | 49 | // owner of contract 50 | address public OWNER; 51 | 52 | // bankroller address 53 | address public BANKROLLER; 54 | 55 | // constructor 56 | function EOSBetSlots() public { 57 | // ledger proof is ALWAYS verified on-chain 58 | oraclize_setProof(proofType_Ledger); 59 | 60 | // gas prices for oraclize call back, can be changed 61 | oraclize_setCustomGasPrice(8000000000); 62 | ORACLIZEGASPRICE = 8000000000; 63 | INITIALGASFORORACLIZE = 225000; 64 | 65 | AMOUNTWAGERED = 0; 66 | DIALSSPUN = 0; 67 | 68 | GAMEPAUSED = false; 69 | REFUNDSACTIVE = true; 70 | 71 | ORACLIZEQUERYMAXTIME = 6 hours; 72 | MINBET_perSPIN = 2 finney; // currently, this is ~40-50c a spin, which is pretty average slots. This is changeable by OWNER 73 | MINBET_perTX = 10 finney; 74 | MAXWIN_inTHOUSANDTHPERCENTS = 333; // 333/1000 so a jackpot can take 33% of bankroll (extremely rare) 75 | OWNER = msg.sender; 76 | } 77 | 78 | //////////////////////////////////// 79 | // INTERFACE CONTACT FUNCTIONS 80 | //////////////////////////////////// 81 | 82 | function payDevelopersFund(address developer) public { 83 | require(msg.sender == BANKROLLER); 84 | 85 | uint256 devFund = DEVELOPERSFUND; 86 | 87 | DEVELOPERSFUND = 0; 88 | 89 | developer.transfer(devFund); 90 | } 91 | 92 | // just a function to receive eth, only allow the bankroll to use this 93 | function receivePaymentForOraclize() payable public { 94 | require(msg.sender == BANKROLLER); 95 | } 96 | 97 | //////////////////////////////////// 98 | // VIEW FUNCTIONS 99 | //////////////////////////////////// 100 | 101 | function getMaxWin() public view returns(uint256){ 102 | return (SafeMath.mul(EOSBetBankrollInterface(BANKROLLER).getBankroll(), MAXWIN_inTHOUSANDTHPERCENTS) / 1000); 103 | } 104 | 105 | //////////////////////////////////// 106 | // OWNER ONLY FUNCTIONS 107 | //////////////////////////////////// 108 | 109 | // WARNING!!!!! Can only set this function once! 110 | function setBankrollerContractOnce(address bankrollAddress) public { 111 | // require that BANKROLLER address == 0x0 (address not set yet), and coming from owner. 112 | require(msg.sender == OWNER && BANKROLLER == address(0)); 113 | 114 | // check here to make sure that the bankroll contract is legitimate 115 | // just make sure that calling the bankroll contract getBankroll() returns non-zero 116 | 117 | require(EOSBetBankrollInterface(bankrollAddress).getBankroll() != 0); 118 | 119 | BANKROLLER = bankrollAddress; 120 | } 121 | 122 | function transferOwnership(address newOwner) public { 123 | require(msg.sender == OWNER); 124 | 125 | OWNER = newOwner; 126 | } 127 | 128 | function setOraclizeQueryMaxTime(uint256 newTime) public { 129 | require(msg.sender == OWNER); 130 | 131 | ORACLIZEQUERYMAXTIME = newTime; 132 | } 133 | 134 | // store the gas price as a storage variable for easy reference, 135 | // and thne change the gas price using the proper oraclize function 136 | function setOraclizeQueryGasPrice(uint256 gasPrice) public { 137 | require(msg.sender == OWNER); 138 | 139 | ORACLIZEGASPRICE = gasPrice; 140 | oraclize_setCustomGasPrice(gasPrice); 141 | } 142 | 143 | // should be ~160,000 to save eth 144 | function setInitialGasForOraclize(uint256 gasAmt) public { 145 | require(msg.sender == OWNER); 146 | 147 | INITIALGASFORORACLIZE = gasAmt; 148 | } 149 | 150 | function setGamePaused(bool paused) public { 151 | require(msg.sender == OWNER); 152 | 153 | GAMEPAUSED = paused; 154 | } 155 | 156 | function setRefundsActive(bool active) public { 157 | require(msg.sender == OWNER); 158 | 159 | REFUNDSACTIVE = active; 160 | } 161 | 162 | function setMinBetPerSpin(uint256 minBet) public { 163 | require(msg.sender == OWNER && minBet > 1000); 164 | 165 | MINBET_perSPIN = minBet; 166 | } 167 | 168 | function setMinBetPerTx(uint256 minBet) public { 169 | require(msg.sender == OWNER && minBet > 1000); 170 | 171 | MINBET_perTX = minBet; 172 | } 173 | 174 | function setMaxwin(uint16 newMaxWinInThousandthPercents) public { 175 | require(msg.sender == OWNER && newMaxWinInThousandthPercents <= 333); // cannot set max win greater than 1/3 of the bankroll (a jackpot is very rare) 176 | 177 | MAXWIN_inTHOUSANDTHPERCENTS = newMaxWinInThousandthPercents; 178 | } 179 | 180 | // rescue tokens inadvertently sent to the contract address 181 | function ERC20Rescue(address tokenAddress, uint256 amtTokens) public { 182 | require (msg.sender == OWNER); 183 | 184 | ERC20(tokenAddress).transfer(msg.sender, amtTokens); 185 | } 186 | 187 | function refund(bytes32 oraclizeQueryId) public { 188 | // save into memory for cheap access 189 | SlotsGameData memory data = slotsData[oraclizeQueryId]; 190 | 191 | //require that the query time is too slow, bet has not been paid out, and either contract owner or player is calling this function. 192 | require(SafeMath.sub(block.timestamp, data.start) >= ORACLIZEQUERYMAXTIME 193 | && (msg.sender == OWNER || msg.sender == data.player) 194 | && (!data.paidOut) 195 | && data.etherReceived <= LIABILITIES 196 | && data.etherReceived > 0 197 | && REFUNDSACTIVE); 198 | 199 | // set contract data 200 | slotsData[oraclizeQueryId].paidOut = true; 201 | 202 | // subtract etherReceived from these two values because the bet is being refunded 203 | LIABILITIES = SafeMath.sub(LIABILITIES, data.etherReceived); 204 | 205 | // then transfer the original bet to the player. 206 | data.player.transfer(data.etherReceived); 207 | 208 | // finally, log an event saying that the refund has processed. 209 | emit Refund(oraclizeQueryId, data.etherReceived); 210 | } 211 | 212 | function play(uint8 credits) public payable { 213 | // save for future use / gas efficiency 214 | uint256 betPerCredit = msg.value / credits; 215 | 216 | // require that the game is unpaused, and that the credits being purchased are greater than 0 and less than the allowed amount, default: 100 spins 217 | // verify that the bet is less than or equal to the bet limit, so we don't go bankrupt, and that the etherreceived is greater than the minbet. 218 | require(!GAMEPAUSED 219 | && msg.value >= MINBET_perTX 220 | && betPerCredit >= MINBET_perSPIN 221 | && credits > 0 222 | && credits <= 224 223 | && SafeMath.mul(betPerCredit, 5000) <= getMaxWin()); // 5000 is the jackpot payout (max win on a roll) 224 | 225 | // equation for gas to oraclize is: 226 | // gas = (some fixed gas amt) + 3270 * credits 227 | 228 | uint256 gasToSend = INITIALGASFORORACLIZE + (uint256(3270) * credits); 229 | 230 | EOSBetBankrollInterface(BANKROLLER).payOraclize(oraclize_getPrice('random', gasToSend)); 231 | 232 | // oraclize_newRandomDSQuery(delay in seconds, bytes of random data, gas for callback function) 233 | bytes32 oraclizeQueryId = oraclize_newRandomDSQuery(0, 30, gasToSend); 234 | 235 | // add the new slots data to a mapping so that the oraclize __callback can use it later 236 | slotsData[oraclizeQueryId] = SlotsGameData({ 237 | player : msg.sender, 238 | paidOut : false, 239 | start : block.timestamp, 240 | etherReceived : msg.value, 241 | credits : credits 242 | }); 243 | 244 | // add the sent value into liabilities. this should NOT go into the bankroll yet 245 | // and must be quarantined here to prevent timing attacks 246 | LIABILITIES = SafeMath.add(LIABILITIES, msg.value); 247 | 248 | emit BuyCredits(oraclizeQueryId); 249 | } 250 | 251 | function __callback(bytes32 _queryId, string _result, bytes _proof) public { 252 | // get the game data and put into memory 253 | SlotsGameData memory data = slotsData[_queryId]; 254 | 255 | require(msg.sender == oraclize_cbAddress() 256 | && !data.paidOut 257 | && data.player != address(0) 258 | && LIABILITIES >= data.etherReceived); 259 | 260 | // if the proof has failed, immediately refund the player the original bet. 261 | if (oraclize_randomDS_proofVerify__returnCode(_queryId, _result, _proof) != 0){ 262 | 263 | if (REFUNDSACTIVE){ 264 | // set contract data 265 | slotsData[_queryId].paidOut = true; 266 | 267 | // subtract from liabilities and amount wagered, because this bet is being refunded. 268 | LIABILITIES = SafeMath.sub(LIABILITIES, data.etherReceived); 269 | 270 | // transfer the original bet back 271 | data.player.transfer(data.etherReceived); 272 | 273 | // log the refund 274 | emit Refund(_queryId, data.etherReceived); 275 | } 276 | // log the ledger proof fail 277 | emit LedgerProofFailed(_queryId); 278 | 279 | } 280 | else { 281 | // again, this block is almost identical to the previous block in the play(...) function 282 | // instead of duplicating documentation, we will just point out the changes from the other block 283 | uint256 dialsSpun; 284 | 285 | uint8 dial1; 286 | uint8 dial2; 287 | uint8 dial3; 288 | 289 | uint256[] memory logsData = new uint256[](8); 290 | 291 | uint256 payout; 292 | 293 | // must use data.credits instead of credits. 294 | for (uint8 i = 0; i < data.credits; i++){ 295 | 296 | // all dials now use _result, instead of blockhash, this is the main change, and allows Slots to 297 | // accomodate bets of any size, free of possible miner interference 298 | dialsSpun += 1; 299 | dial1 = uint8(uint(keccak256(_result, dialsSpun)) % 64); 300 | 301 | dialsSpun += 1; 302 | dial2 = uint8(uint(keccak256(_result, dialsSpun)) % 64); 303 | 304 | dialsSpun += 1; 305 | dial3 = uint8(uint(keccak256(_result, dialsSpun)) % 64); 306 | 307 | // dial 1 308 | dial1 = getDial1Type(dial1); 309 | 310 | // dial 2 311 | dial2 = getDial2Type(dial2); 312 | 313 | // dial 3 314 | dial3 = getDial3Type(dial3); 315 | 316 | // determine the payout 317 | payout += determinePayout(dial1, dial2, dial3); 318 | 319 | // assembling log data 320 | if (i <= 27){ 321 | // in logsData0 322 | logsData[0] += uint256(dial1) * uint256(2) ** (3 * ((3 * (27 - i)) + 2)); 323 | logsData[0] += uint256(dial2) * uint256(2) ** (3 * ((3 * (27 - i)) + 1)); 324 | logsData[0] += uint256(dial3) * uint256(2) ** (3 * ((3 * (27 - i)))); 325 | } 326 | else if (i <= 55){ 327 | // in logsData1 328 | logsData[1] += uint256(dial1) * uint256(2) ** (3 * ((3 * (55 - i)) + 2)); 329 | logsData[1] += uint256(dial2) * uint256(2) ** (3 * ((3 * (55 - i)) + 1)); 330 | logsData[1] += uint256(dial3) * uint256(2) ** (3 * ((3 * (55 - i)))); 331 | } 332 | else if (i <= 83) { 333 | // in logsData2 334 | logsData[2] += uint256(dial1) * uint256(2) ** (3 * ((3 * (83 - i)) + 2)); 335 | logsData[2] += uint256(dial2) * uint256(2) ** (3 * ((3 * (83 - i)) + 1)); 336 | logsData[2] += uint256(dial3) * uint256(2) ** (3 * ((3 * (83 - i)))); 337 | } 338 | else if (i <= 111) { 339 | // in logsData3 340 | logsData[3] += uint256(dial1) * uint256(2) ** (3 * ((3 * (111 - i)) + 2)); 341 | logsData[3] += uint256(dial2) * uint256(2) ** (3 * ((3 * (111 - i)) + 1)); 342 | logsData[3] += uint256(dial3) * uint256(2) ** (3 * ((3 * (111 - i)))); 343 | } 344 | else if (i <= 139){ 345 | // in logsData4 346 | logsData[4] += uint256(dial1) * uint256(2) ** (3 * ((3 * (139 - i)) + 2)); 347 | logsData[4] += uint256(dial2) * uint256(2) ** (3 * ((3 * (139 - i)) + 1)); 348 | logsData[4] += uint256(dial3) * uint256(2) ** (3 * ((3 * (139 - i)))); 349 | } 350 | else if (i <= 167){ 351 | // in logsData5 352 | logsData[5] += uint256(dial1) * uint256(2) ** (3 * ((3 * (167 - i)) + 2)); 353 | logsData[5] += uint256(dial2) * uint256(2) ** (3 * ((3 * (167 - i)) + 1)); 354 | logsData[5] += uint256(dial3) * uint256(2) ** (3 * ((3 * (167 - i)))); 355 | } 356 | else if (i <= 195){ 357 | // in logsData6 358 | logsData[6] += uint256(dial1) * uint256(2) ** (3 * ((3 * (195 - i)) + 2)); 359 | logsData[6] += uint256(dial2) * uint256(2) ** (3 * ((3 * (195 - i)) + 1)); 360 | logsData[6] += uint256(dial3) * uint256(2) ** (3 * ((3 * (195 - i)))); 361 | } 362 | else if (i <= 223){ 363 | // in logsData7 364 | logsData[7] += uint256(dial1) * uint256(2) ** (3 * ((3 * (223 - i)) + 2)); 365 | logsData[7] += uint256(dial2) * uint256(2) ** (3 * ((3 * (223 - i)) + 1)); 366 | logsData[7] += uint256(dial3) * uint256(2) ** (3 * ((3 * (223 - i)))); 367 | } 368 | } 369 | 370 | // track that these spins were made 371 | DIALSSPUN += dialsSpun; 372 | 373 | // and add the amount wagered 374 | AMOUNTWAGERED = SafeMath.add(AMOUNTWAGERED, data.etherReceived); 375 | 376 | // IMPORTANT: we must change the "paidOut" to TRUE here to prevent reentrancy/other nasty effects. 377 | // this was not needed with the previous loop/code block, and is used because variables must be written into storage 378 | slotsData[_queryId].paidOut = true; 379 | 380 | // decrease LIABILITIES when the spins are made 381 | LIABILITIES = SafeMath.sub(LIABILITIES, data.etherReceived); 382 | 383 | // get the developers cut, and send the rest of the ether received to the bankroller contract 384 | uint256 developersCut = data.etherReceived / 100; 385 | 386 | DEVELOPERSFUND = SafeMath.add(DEVELOPERSFUND, developersCut); 387 | 388 | EOSBetBankrollInterface(BANKROLLER).receiveEtherFromGameAddress.value(SafeMath.sub(data.etherReceived, developersCut))(); 389 | 390 | // get the ether to be paid out, and force the bankroller contract to pay out the player 391 | uint256 etherPaidout = SafeMath.mul((data.etherReceived / data.credits), payout); 392 | 393 | EOSBetBankrollInterface(BANKROLLER).payEtherToWinner(etherPaidout, data.player); 394 | 395 | // log en event with indexed query id 396 | emit SlotsLargeBet(_queryId, logsData[0], logsData[1], logsData[2], logsData[3], logsData[4], logsData[5], logsData[6], logsData[7]); 397 | } 398 | } 399 | 400 | // HELPER FUNCTIONS TO: 401 | // calculate the result of the dials based on the hardcoded slot data: 402 | 403 | // STOPS REEL#1 REEL#2 REEL#3 404 | /////////////////////////////////////////// 405 | // gold ether 0 // 1 // 3 // 1 // 406 | // silver ether 1 // 7 // 1 // 6 // 407 | // bronze ether 2 // 1 // 7 // 6 // 408 | // gold planet 3 // 5 // 7 // 6 // 409 | // silverplanet 4 // 9 // 6 // 7 // 410 | // bronzeplanet 5 // 9 // 8 // 6 // 411 | // ---blank--- 6 // 32 // 32 // 32 // 412 | /////////////////////////////////////////// 413 | 414 | // note that dial1 / 2 / 3 will go from mod 64 to mod 7 in this manner 415 | 416 | function getDial1Type(uint8 dial1Location) internal pure returns(uint8) { 417 | if (dial1Location == 0) { return 0; } 418 | else if (dial1Location >= 1 && dial1Location <= 7) { return 1; } 419 | else if (dial1Location == 8) { return 2; } 420 | else if (dial1Location >= 9 && dial1Location <= 13) { return 3; } 421 | else if (dial1Location >= 14 && dial1Location <= 22) { return 4; } 422 | else if (dial1Location >= 23 && dial1Location <= 31) { return 5; } 423 | else { return 6; } 424 | } 425 | 426 | function getDial2Type(uint8 dial2Location) internal pure returns(uint8) { 427 | if (dial2Location >= 0 && dial2Location <= 2) { return 0; } 428 | else if (dial2Location == 3) { return 1; } 429 | else if (dial2Location >= 4 && dial2Location <= 10) { return 2; } 430 | else if (dial2Location >= 11 && dial2Location <= 17) { return 3; } 431 | else if (dial2Location >= 18 && dial2Location <= 23) { return 4; } 432 | else if (dial2Location >= 24 && dial2Location <= 31) { return 5; } 433 | else { return 6; } 434 | } 435 | 436 | function getDial3Type(uint8 dial3Location) internal pure returns(uint8) { 437 | if (dial3Location == 0) { return 0; } 438 | else if (dial3Location >= 1 && dial3Location <= 6) { return 1; } 439 | else if (dial3Location >= 7 && dial3Location <= 12) { return 2; } 440 | else if (dial3Location >= 13 && dial3Location <= 18) { return 3; } 441 | else if (dial3Location >= 19 && dial3Location <= 25) { return 4; } 442 | else if (dial3Location >= 26 && dial3Location <= 31) { return 5; } 443 | else { return 6; } 444 | } 445 | 446 | // HELPER FUNCTION TO: 447 | // determine the payout given dial locations based on this table 448 | 449 | // hardcoded payouts data: 450 | // LANDS ON // PAYS // 451 | //////////////////////////////////////////////// 452 | // Bronze E -> Silver E -> Gold E // 5000 // 453 | // 3x Gold Ether // 1777 // 454 | // 3x Silver Ether // 250 // 455 | // 3x Bronze Ether // 250 // 456 | // Bronze P -> Silver P -> Gold P // 90 // 457 | // 3x any Ether // 70 // 458 | // 3x Gold Planet // 50 // 459 | // 3x Silver Planet // 25 // 460 | // Any Gold P & Silver P & Bronze P // 15 // 461 | // 3x Bronze Planet // 10 // 462 | // Any 3 planet type // 3 // 463 | // Any 3 gold // 3 // 464 | // Any 3 silver // 2 // 465 | // Any 3 bronze // 2 // 466 | // Blank, blank, blank // 1 // 467 | // else // 0 // 468 | //////////////////////////////////////////////// 469 | 470 | function determinePayout(uint8 dial1, uint8 dial2, uint8 dial3) internal pure returns(uint256) { 471 | if (dial1 == 6 || dial2 == 6 || dial3 == 6){ 472 | // all blank 473 | if (dial1 == 6 && dial2 == 6 && dial3 == 6) 474 | return 1; 475 | } 476 | else if (dial1 == 5){ 477 | // bronze planet -> silver planet -> gold planet 478 | if (dial2 == 4 && dial3 == 3) 479 | return 90; 480 | 481 | // one gold planet, one silver planet, one bronze planet, any order! 482 | // note: other order covered above, return 90 483 | else if (dial2 == 3 && dial3 == 4) 484 | return 15; 485 | 486 | // all bronze planet 487 | else if (dial2 == 5 && dial3 == 5) 488 | return 10; 489 | 490 | // any three planet type 491 | else if (dial2 >= 3 && dial2 <= 5 && dial3 >= 3 && dial3 <= 5) 492 | return 3; 493 | 494 | // any three bronze 495 | else if ((dial2 == 2 || dial2 == 5) && (dial3 == 2 || dial3 == 5)) 496 | return 2; 497 | } 498 | else if (dial1 == 4){ 499 | // all silver planet 500 | if (dial2 == 4 && dial3 == 4) 501 | return 25; 502 | 503 | // one gold planet, one silver planet, one bronze planet, any order! 504 | else if ((dial2 == 3 && dial3 == 5) || (dial2 == 5 && dial3 == 3)) 505 | return 15; 506 | 507 | // any three planet type 508 | else if (dial2 >= 3 && dial2 <= 5 && dial3 >= 3 && dial3 <= 5) 509 | return 3; 510 | 511 | // any three silver 512 | else if ((dial2 == 1 || dial2 == 4) && (dial3 == 1 || dial3 == 4)) 513 | return 2; 514 | } 515 | else if (dial1 == 3){ 516 | // all gold planet 517 | if (dial2 == 3 && dial3 == 3) 518 | return 50; 519 | 520 | // one gold planet, one silver planet, one bronze planet, any order! 521 | else if ((dial2 == 4 && dial3 == 5) || (dial2 == 5 && dial3 == 4)) 522 | return 15; 523 | 524 | // any three planet type 525 | else if (dial2 >= 3 && dial2 <= 5 && dial3 >= 3 && dial3 <= 5) 526 | return 3; 527 | 528 | // any three gold 529 | else if ((dial2 == 0 || dial2 == 3) && (dial3 == 0 || dial3 == 3)) 530 | return 3; 531 | } 532 | else if (dial1 == 2){ 533 | if (dial2 == 1 && dial3 == 0) 534 | return 5000; // jackpot!!!! 535 | 536 | // all bronze ether 537 | else if (dial2 == 2 && dial3 == 2) 538 | return 250; 539 | 540 | // all some type of ether 541 | else if (dial2 >= 0 && dial2 <= 2 && dial3 >= 0 && dial3 <= 2) 542 | return 70; 543 | 544 | // any three bronze 545 | else if ((dial2 == 2 || dial2 == 5) && (dial3 == 2 || dial3 == 5)) 546 | return 2; 547 | } 548 | else if (dial1 == 1){ 549 | // all silver ether 550 | if (dial2 == 1 && dial3 == 1) 551 | return 250; 552 | 553 | // all some type of ether 554 | else if (dial2 >= 0 && dial2 <= 2 && dial3 >= 0 && dial3 <= 2) 555 | return 70; 556 | 557 | // any three silver 558 | else if ((dial2 == 1 || dial2 == 4) && (dial3 == 1 || dial3 == 4)) 559 | return 3; 560 | } 561 | else if (dial1 == 0){ 562 | // all gold ether 563 | if (dial2 == 0 && dial3 == 0) 564 | return 1777; 565 | 566 | // all some type of ether 567 | else if (dial2 >= 0 && dial2 <= 2 && dial3 >= 0 && dial3 <= 2) 568 | return 70; 569 | 570 | // any three gold 571 | else if ((dial2 == 0 || dial2 == 3) && (dial3 == 0 || dial3 == 3)) 572 | return 3; 573 | } 574 | return 0; 575 | } 576 | 577 | } -------------------------------------------------------------------------------- /contracts/usingOraclize.sol: -------------------------------------------------------------------------------- 1 | // 2 | /* 3 | Copyright (c) 2015-2016 Oraclize SRL 4 | Copyright (c) 2016 Oraclize LTD 5 | 6 | 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | 16 | 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | 20 | 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | THE SOFTWARE. 29 | */ 30 | 31 | // This api is currently targeted at 0.4.18, please import oraclizeAPI_pre0.4.sol or oraclizeAPI_0.4 where necessary 32 | pragma solidity ^0.4.18; 33 | 34 | contract OraclizeI { 35 | address public cbAddress; 36 | function query(uint _timestamp, string _datasource, string _arg) external payable returns (bytes32 _id); 37 | function query_withGasLimit(uint _timestamp, string _datasource, string _arg, uint _gaslimit) external payable returns (bytes32 _id); 38 | function query2(uint _timestamp, string _datasource, string _arg1, string _arg2) public payable returns (bytes32 _id); 39 | function query2_withGasLimit(uint _timestamp, string _datasource, string _arg1, string _arg2, uint _gaslimit) external payable returns (bytes32 _id); 40 | function queryN(uint _timestamp, string _datasource, bytes _argN) public payable returns (bytes32 _id); 41 | function queryN_withGasLimit(uint _timestamp, string _datasource, bytes _argN, uint _gaslimit) external payable returns (bytes32 _id); 42 | function getPrice(string _datasource) public returns (uint _dsprice); 43 | function getPrice(string _datasource, uint gaslimit) public returns (uint _dsprice); 44 | function setProofType(byte _proofType) external; 45 | function setCustomGasPrice(uint _gasPrice) external; 46 | function randomDS_getSessionPubKeyHash() external constant returns(bytes32); 47 | } 48 | contract OraclizeAddrResolverI { 49 | function getAddress() public returns (address _addr); 50 | } 51 | contract usingOraclize { 52 | uint constant day = 60*60*24; 53 | uint constant week = 60*60*24*7; 54 | uint constant month = 60*60*24*30; 55 | byte constant proofType_NONE = 0x00; 56 | byte constant proofType_TLSNotary = 0x10; 57 | byte constant proofType_Android = 0x20; 58 | byte constant proofType_Ledger = 0x30; 59 | byte constant proofType_Native = 0xF0; 60 | byte constant proofStorage_IPFS = 0x01; 61 | uint8 constant networkID_auto = 0; 62 | uint8 constant networkID_mainnet = 1; 63 | uint8 constant networkID_testnet = 2; 64 | uint8 constant networkID_morden = 2; 65 | uint8 constant networkID_consensys = 161; 66 | 67 | OraclizeAddrResolverI OAR; 68 | 69 | OraclizeI oraclize; 70 | modifier oraclizeAPI { 71 | if((address(OAR)==0)||(getCodeSize(address(OAR))==0)) 72 | oraclize_setNetwork(networkID_auto); 73 | 74 | if(address(oraclize) != OAR.getAddress()) 75 | oraclize = OraclizeI(OAR.getAddress()); 76 | 77 | _; 78 | } 79 | modifier coupon(string code){ 80 | oraclize = OraclizeI(OAR.getAddress()); 81 | _; 82 | } 83 | 84 | function oraclize_setNetwork(uint8 networkID) internal returns(bool){ 85 | return oraclize_setNetwork(); 86 | networkID; // silence the warning and remain backwards compatible 87 | } 88 | function oraclize_setNetwork() internal returns(bool){ 89 | if (getCodeSize(0x1d3B2638a7cC9f2CB3D298A3DA7a90B67E5506ed)>0){ //mainnet 90 | OAR = OraclizeAddrResolverI(0x1d3B2638a7cC9f2CB3D298A3DA7a90B67E5506ed); 91 | oraclize_setNetworkName("eth_mainnet"); 92 | return true; 93 | } 94 | if (getCodeSize(0xc03A2615D5efaf5F49F60B7BB6583eaec212fdf1)>0){ //ropsten testnet 95 | OAR = OraclizeAddrResolverI(0xc03A2615D5efaf5F49F60B7BB6583eaec212fdf1); 96 | oraclize_setNetworkName("eth_ropsten3"); 97 | return true; 98 | } 99 | if (getCodeSize(0xB7A07BcF2Ba2f2703b24C0691b5278999C59AC7e)>0){ //kovan testnet 100 | OAR = OraclizeAddrResolverI(0xB7A07BcF2Ba2f2703b24C0691b5278999C59AC7e); 101 | oraclize_setNetworkName("eth_kovan"); 102 | return true; 103 | } 104 | if (getCodeSize(0x146500cfd35B22E4A392Fe0aDc06De1a1368Ed48)>0){ //rinkeby testnet 105 | OAR = OraclizeAddrResolverI(0x146500cfd35B22E4A392Fe0aDc06De1a1368Ed48); 106 | oraclize_setNetworkName("eth_rinkeby"); 107 | return true; 108 | } 109 | if (getCodeSize(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475)>0){ //ethereum-bridge 110 | OAR = OraclizeAddrResolverI(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475); 111 | return true; 112 | } 113 | if (getCodeSize(0x20e12A1F859B3FeaE5Fb2A0A32C18F5a65555bBF)>0){ //ether.camp ide 114 | OAR = OraclizeAddrResolverI(0x20e12A1F859B3FeaE5Fb2A0A32C18F5a65555bBF); 115 | return true; 116 | } 117 | if (getCodeSize(0x51efaF4c8B3C9AfBD5aB9F4bbC82784Ab6ef8fAA)>0){ //browser-solidity 118 | OAR = OraclizeAddrResolverI(0x51efaF4c8B3C9AfBD5aB9F4bbC82784Ab6ef8fAA); 119 | return true; 120 | } 121 | return false; 122 | } 123 | 124 | function __callback(bytes32 myid, string result) public { 125 | __callback(myid, result, new bytes(0)); 126 | } 127 | function __callback(bytes32 myid, string result, bytes proof) public { 128 | return; 129 | myid; result; proof; // Silence compiler warnings 130 | } 131 | 132 | function oraclize_getPrice(string datasource) oraclizeAPI internal returns (uint){ 133 | return oraclize.getPrice(datasource); 134 | } 135 | 136 | function oraclize_getPrice(string datasource, uint gaslimit) oraclizeAPI internal returns (uint){ 137 | return oraclize.getPrice(datasource, gaslimit); 138 | } 139 | 140 | function oraclize_query(string datasource, string arg) oraclizeAPI internal returns (bytes32 id){ 141 | uint price = oraclize.getPrice(datasource); 142 | if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price 143 | return oraclize.query.value(price)(0, datasource, arg); 144 | } 145 | function oraclize_query(uint timestamp, string datasource, string arg) oraclizeAPI internal returns (bytes32 id){ 146 | uint price = oraclize.getPrice(datasource); 147 | if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price 148 | return oraclize.query.value(price)(timestamp, datasource, arg); 149 | } 150 | function oraclize_query(uint timestamp, string datasource, string arg, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ 151 | uint price = oraclize.getPrice(datasource, gaslimit); 152 | if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price 153 | return oraclize.query_withGasLimit.value(price)(timestamp, datasource, arg, gaslimit); 154 | } 155 | function oraclize_query(string datasource, string arg, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ 156 | uint price = oraclize.getPrice(datasource, gaslimit); 157 | if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price 158 | return oraclize.query_withGasLimit.value(price)(0, datasource, arg, gaslimit); 159 | } 160 | function oraclize_query(string datasource, string arg1, string arg2) oraclizeAPI internal returns (bytes32 id){ 161 | uint price = oraclize.getPrice(datasource); 162 | if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price 163 | return oraclize.query2.value(price)(0, datasource, arg1, arg2); 164 | } 165 | function oraclize_query(uint timestamp, string datasource, string arg1, string arg2) oraclizeAPI internal returns (bytes32 id){ 166 | uint price = oraclize.getPrice(datasource); 167 | if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price 168 | return oraclize.query2.value(price)(timestamp, datasource, arg1, arg2); 169 | } 170 | function oraclize_query(uint timestamp, string datasource, string arg1, string arg2, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ 171 | uint price = oraclize.getPrice(datasource, gaslimit); 172 | if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price 173 | return oraclize.query2_withGasLimit.value(price)(timestamp, datasource, arg1, arg2, gaslimit); 174 | } 175 | function oraclize_query(string datasource, string arg1, string arg2, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ 176 | uint price = oraclize.getPrice(datasource, gaslimit); 177 | if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price 178 | return oraclize.query2_withGasLimit.value(price)(0, datasource, arg1, arg2, gaslimit); 179 | } 180 | function oraclize_query(string datasource, string[] argN) oraclizeAPI internal returns (bytes32 id){ 181 | uint price = oraclize.getPrice(datasource); 182 | if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price 183 | bytes memory args = stra2cbor(argN); 184 | return oraclize.queryN.value(price)(0, datasource, args); 185 | } 186 | function oraclize_query(uint timestamp, string datasource, string[] argN) oraclizeAPI internal returns (bytes32 id){ 187 | uint price = oraclize.getPrice(datasource); 188 | if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price 189 | bytes memory args = stra2cbor(argN); 190 | return oraclize.queryN.value(price)(timestamp, datasource, args); 191 | } 192 | function oraclize_query(uint timestamp, string datasource, string[] argN, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ 193 | uint price = oraclize.getPrice(datasource, gaslimit); 194 | if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price 195 | bytes memory args = stra2cbor(argN); 196 | return oraclize.queryN_withGasLimit.value(price)(timestamp, datasource, args, gaslimit); 197 | } 198 | function oraclize_query(string datasource, string[] argN, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ 199 | uint price = oraclize.getPrice(datasource, gaslimit); 200 | if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price 201 | bytes memory args = stra2cbor(argN); 202 | return oraclize.queryN_withGasLimit.value(price)(0, datasource, args, gaslimit); 203 | } 204 | function oraclize_query(string datasource, string[1] args) oraclizeAPI internal returns (bytes32 id) { 205 | string[] memory dynargs = new string[](1); 206 | dynargs[0] = args[0]; 207 | return oraclize_query(datasource, dynargs); 208 | } 209 | function oraclize_query(uint timestamp, string datasource, string[1] args) oraclizeAPI internal returns (bytes32 id) { 210 | string[] memory dynargs = new string[](1); 211 | dynargs[0] = args[0]; 212 | return oraclize_query(timestamp, datasource, dynargs); 213 | } 214 | function oraclize_query(uint timestamp, string datasource, string[1] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 215 | string[] memory dynargs = new string[](1); 216 | dynargs[0] = args[0]; 217 | return oraclize_query(timestamp, datasource, dynargs, gaslimit); 218 | } 219 | function oraclize_query(string datasource, string[1] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 220 | string[] memory dynargs = new string[](1); 221 | dynargs[0] = args[0]; 222 | return oraclize_query(datasource, dynargs, gaslimit); 223 | } 224 | 225 | function oraclize_query(string datasource, string[2] args) oraclizeAPI internal returns (bytes32 id) { 226 | string[] memory dynargs = new string[](2); 227 | dynargs[0] = args[0]; 228 | dynargs[1] = args[1]; 229 | return oraclize_query(datasource, dynargs); 230 | } 231 | function oraclize_query(uint timestamp, string datasource, string[2] args) oraclizeAPI internal returns (bytes32 id) { 232 | string[] memory dynargs = new string[](2); 233 | dynargs[0] = args[0]; 234 | dynargs[1] = args[1]; 235 | return oraclize_query(timestamp, datasource, dynargs); 236 | } 237 | function oraclize_query(uint timestamp, string datasource, string[2] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 238 | string[] memory dynargs = new string[](2); 239 | dynargs[0] = args[0]; 240 | dynargs[1] = args[1]; 241 | return oraclize_query(timestamp, datasource, dynargs, gaslimit); 242 | } 243 | function oraclize_query(string datasource, string[2] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 244 | string[] memory dynargs = new string[](2); 245 | dynargs[0] = args[0]; 246 | dynargs[1] = args[1]; 247 | return oraclize_query(datasource, dynargs, gaslimit); 248 | } 249 | function oraclize_query(string datasource, string[3] args) oraclizeAPI internal returns (bytes32 id) { 250 | string[] memory dynargs = new string[](3); 251 | dynargs[0] = args[0]; 252 | dynargs[1] = args[1]; 253 | dynargs[2] = args[2]; 254 | return oraclize_query(datasource, dynargs); 255 | } 256 | function oraclize_query(uint timestamp, string datasource, string[3] args) oraclizeAPI internal returns (bytes32 id) { 257 | string[] memory dynargs = new string[](3); 258 | dynargs[0] = args[0]; 259 | dynargs[1] = args[1]; 260 | dynargs[2] = args[2]; 261 | return oraclize_query(timestamp, datasource, dynargs); 262 | } 263 | function oraclize_query(uint timestamp, string datasource, string[3] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 264 | string[] memory dynargs = new string[](3); 265 | dynargs[0] = args[0]; 266 | dynargs[1] = args[1]; 267 | dynargs[2] = args[2]; 268 | return oraclize_query(timestamp, datasource, dynargs, gaslimit); 269 | } 270 | function oraclize_query(string datasource, string[3] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 271 | string[] memory dynargs = new string[](3); 272 | dynargs[0] = args[0]; 273 | dynargs[1] = args[1]; 274 | dynargs[2] = args[2]; 275 | return oraclize_query(datasource, dynargs, gaslimit); 276 | } 277 | 278 | function oraclize_query(string datasource, string[4] args) oraclizeAPI internal returns (bytes32 id) { 279 | string[] memory dynargs = new string[](4); 280 | dynargs[0] = args[0]; 281 | dynargs[1] = args[1]; 282 | dynargs[2] = args[2]; 283 | dynargs[3] = args[3]; 284 | return oraclize_query(datasource, dynargs); 285 | } 286 | function oraclize_query(uint timestamp, string datasource, string[4] args) oraclizeAPI internal returns (bytes32 id) { 287 | string[] memory dynargs = new string[](4); 288 | dynargs[0] = args[0]; 289 | dynargs[1] = args[1]; 290 | dynargs[2] = args[2]; 291 | dynargs[3] = args[3]; 292 | return oraclize_query(timestamp, datasource, dynargs); 293 | } 294 | function oraclize_query(uint timestamp, string datasource, string[4] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 295 | string[] memory dynargs = new string[](4); 296 | dynargs[0] = args[0]; 297 | dynargs[1] = args[1]; 298 | dynargs[2] = args[2]; 299 | dynargs[3] = args[3]; 300 | return oraclize_query(timestamp, datasource, dynargs, gaslimit); 301 | } 302 | function oraclize_query(string datasource, string[4] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 303 | string[] memory dynargs = new string[](4); 304 | dynargs[0] = args[0]; 305 | dynargs[1] = args[1]; 306 | dynargs[2] = args[2]; 307 | dynargs[3] = args[3]; 308 | return oraclize_query(datasource, dynargs, gaslimit); 309 | } 310 | function oraclize_query(string datasource, string[5] args) oraclizeAPI internal returns (bytes32 id) { 311 | string[] memory dynargs = new string[](5); 312 | dynargs[0] = args[0]; 313 | dynargs[1] = args[1]; 314 | dynargs[2] = args[2]; 315 | dynargs[3] = args[3]; 316 | dynargs[4] = args[4]; 317 | return oraclize_query(datasource, dynargs); 318 | } 319 | function oraclize_query(uint timestamp, string datasource, string[5] args) oraclizeAPI internal returns (bytes32 id) { 320 | string[] memory dynargs = new string[](5); 321 | dynargs[0] = args[0]; 322 | dynargs[1] = args[1]; 323 | dynargs[2] = args[2]; 324 | dynargs[3] = args[3]; 325 | dynargs[4] = args[4]; 326 | return oraclize_query(timestamp, datasource, dynargs); 327 | } 328 | function oraclize_query(uint timestamp, string datasource, string[5] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 329 | string[] memory dynargs = new string[](5); 330 | dynargs[0] = args[0]; 331 | dynargs[1] = args[1]; 332 | dynargs[2] = args[2]; 333 | dynargs[3] = args[3]; 334 | dynargs[4] = args[4]; 335 | return oraclize_query(timestamp, datasource, dynargs, gaslimit); 336 | } 337 | function oraclize_query(string datasource, string[5] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 338 | string[] memory dynargs = new string[](5); 339 | dynargs[0] = args[0]; 340 | dynargs[1] = args[1]; 341 | dynargs[2] = args[2]; 342 | dynargs[3] = args[3]; 343 | dynargs[4] = args[4]; 344 | return oraclize_query(datasource, dynargs, gaslimit); 345 | } 346 | function oraclize_query(string datasource, bytes[] argN) oraclizeAPI internal returns (bytes32 id){ 347 | uint price = oraclize.getPrice(datasource); 348 | if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price 349 | bytes memory args = ba2cbor(argN); 350 | return oraclize.queryN.value(price)(0, datasource, args); 351 | } 352 | function oraclize_query(uint timestamp, string datasource, bytes[] argN) oraclizeAPI internal returns (bytes32 id){ 353 | uint price = oraclize.getPrice(datasource); 354 | if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price 355 | bytes memory args = ba2cbor(argN); 356 | return oraclize.queryN.value(price)(timestamp, datasource, args); 357 | } 358 | function oraclize_query(uint timestamp, string datasource, bytes[] argN, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ 359 | uint price = oraclize.getPrice(datasource, gaslimit); 360 | if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price 361 | bytes memory args = ba2cbor(argN); 362 | return oraclize.queryN_withGasLimit.value(price)(timestamp, datasource, args, gaslimit); 363 | } 364 | function oraclize_query(string datasource, bytes[] argN, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ 365 | uint price = oraclize.getPrice(datasource, gaslimit); 366 | if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price 367 | bytes memory args = ba2cbor(argN); 368 | return oraclize.queryN_withGasLimit.value(price)(0, datasource, args, gaslimit); 369 | } 370 | function oraclize_query(string datasource, bytes[1] args) oraclizeAPI internal returns (bytes32 id) { 371 | bytes[] memory dynargs = new bytes[](1); 372 | dynargs[0] = args[0]; 373 | return oraclize_query(datasource, dynargs); 374 | } 375 | function oraclize_query(uint timestamp, string datasource, bytes[1] args) oraclizeAPI internal returns (bytes32 id) { 376 | bytes[] memory dynargs = new bytes[](1); 377 | dynargs[0] = args[0]; 378 | return oraclize_query(timestamp, datasource, dynargs); 379 | } 380 | function oraclize_query(uint timestamp, string datasource, bytes[1] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 381 | bytes[] memory dynargs = new bytes[](1); 382 | dynargs[0] = args[0]; 383 | return oraclize_query(timestamp, datasource, dynargs, gaslimit); 384 | } 385 | function oraclize_query(string datasource, bytes[1] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 386 | bytes[] memory dynargs = new bytes[](1); 387 | dynargs[0] = args[0]; 388 | return oraclize_query(datasource, dynargs, gaslimit); 389 | } 390 | 391 | function oraclize_query(string datasource, bytes[2] args) oraclizeAPI internal returns (bytes32 id) { 392 | bytes[] memory dynargs = new bytes[](2); 393 | dynargs[0] = args[0]; 394 | dynargs[1] = args[1]; 395 | return oraclize_query(datasource, dynargs); 396 | } 397 | function oraclize_query(uint timestamp, string datasource, bytes[2] args) oraclizeAPI internal returns (bytes32 id) { 398 | bytes[] memory dynargs = new bytes[](2); 399 | dynargs[0] = args[0]; 400 | dynargs[1] = args[1]; 401 | return oraclize_query(timestamp, datasource, dynargs); 402 | } 403 | function oraclize_query(uint timestamp, string datasource, bytes[2] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 404 | bytes[] memory dynargs = new bytes[](2); 405 | dynargs[0] = args[0]; 406 | dynargs[1] = args[1]; 407 | return oraclize_query(timestamp, datasource, dynargs, gaslimit); 408 | } 409 | function oraclize_query(string datasource, bytes[2] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 410 | bytes[] memory dynargs = new bytes[](2); 411 | dynargs[0] = args[0]; 412 | dynargs[1] = args[1]; 413 | return oraclize_query(datasource, dynargs, gaslimit); 414 | } 415 | function oraclize_query(string datasource, bytes[3] args) oraclizeAPI internal returns (bytes32 id) { 416 | bytes[] memory dynargs = new bytes[](3); 417 | dynargs[0] = args[0]; 418 | dynargs[1] = args[1]; 419 | dynargs[2] = args[2]; 420 | return oraclize_query(datasource, dynargs); 421 | } 422 | function oraclize_query(uint timestamp, string datasource, bytes[3] args) oraclizeAPI internal returns (bytes32 id) { 423 | bytes[] memory dynargs = new bytes[](3); 424 | dynargs[0] = args[0]; 425 | dynargs[1] = args[1]; 426 | dynargs[2] = args[2]; 427 | return oraclize_query(timestamp, datasource, dynargs); 428 | } 429 | function oraclize_query(uint timestamp, string datasource, bytes[3] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 430 | bytes[] memory dynargs = new bytes[](3); 431 | dynargs[0] = args[0]; 432 | dynargs[1] = args[1]; 433 | dynargs[2] = args[2]; 434 | return oraclize_query(timestamp, datasource, dynargs, gaslimit); 435 | } 436 | function oraclize_query(string datasource, bytes[3] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 437 | bytes[] memory dynargs = new bytes[](3); 438 | dynargs[0] = args[0]; 439 | dynargs[1] = args[1]; 440 | dynargs[2] = args[2]; 441 | return oraclize_query(datasource, dynargs, gaslimit); 442 | } 443 | 444 | function oraclize_query(string datasource, bytes[4] args) oraclizeAPI internal returns (bytes32 id) { 445 | bytes[] memory dynargs = new bytes[](4); 446 | dynargs[0] = args[0]; 447 | dynargs[1] = args[1]; 448 | dynargs[2] = args[2]; 449 | dynargs[3] = args[3]; 450 | return oraclize_query(datasource, dynargs); 451 | } 452 | function oraclize_query(uint timestamp, string datasource, bytes[4] args) oraclizeAPI internal returns (bytes32 id) { 453 | bytes[] memory dynargs = new bytes[](4); 454 | dynargs[0] = args[0]; 455 | dynargs[1] = args[1]; 456 | dynargs[2] = args[2]; 457 | dynargs[3] = args[3]; 458 | return oraclize_query(timestamp, datasource, dynargs); 459 | } 460 | function oraclize_query(uint timestamp, string datasource, bytes[4] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 461 | bytes[] memory dynargs = new bytes[](4); 462 | dynargs[0] = args[0]; 463 | dynargs[1] = args[1]; 464 | dynargs[2] = args[2]; 465 | dynargs[3] = args[3]; 466 | return oraclize_query(timestamp, datasource, dynargs, gaslimit); 467 | } 468 | function oraclize_query(string datasource, bytes[4] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 469 | bytes[] memory dynargs = new bytes[](4); 470 | dynargs[0] = args[0]; 471 | dynargs[1] = args[1]; 472 | dynargs[2] = args[2]; 473 | dynargs[3] = args[3]; 474 | return oraclize_query(datasource, dynargs, gaslimit); 475 | } 476 | function oraclize_query(string datasource, bytes[5] args) oraclizeAPI internal returns (bytes32 id) { 477 | bytes[] memory dynargs = new bytes[](5); 478 | dynargs[0] = args[0]; 479 | dynargs[1] = args[1]; 480 | dynargs[2] = args[2]; 481 | dynargs[3] = args[3]; 482 | dynargs[4] = args[4]; 483 | return oraclize_query(datasource, dynargs); 484 | } 485 | function oraclize_query(uint timestamp, string datasource, bytes[5] args) oraclizeAPI internal returns (bytes32 id) { 486 | bytes[] memory dynargs = new bytes[](5); 487 | dynargs[0] = args[0]; 488 | dynargs[1] = args[1]; 489 | dynargs[2] = args[2]; 490 | dynargs[3] = args[3]; 491 | dynargs[4] = args[4]; 492 | return oraclize_query(timestamp, datasource, dynargs); 493 | } 494 | function oraclize_query(uint timestamp, string datasource, bytes[5] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 495 | bytes[] memory dynargs = new bytes[](5); 496 | dynargs[0] = args[0]; 497 | dynargs[1] = args[1]; 498 | dynargs[2] = args[2]; 499 | dynargs[3] = args[3]; 500 | dynargs[4] = args[4]; 501 | return oraclize_query(timestamp, datasource, dynargs, gaslimit); 502 | } 503 | function oraclize_query(string datasource, bytes[5] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { 504 | bytes[] memory dynargs = new bytes[](5); 505 | dynargs[0] = args[0]; 506 | dynargs[1] = args[1]; 507 | dynargs[2] = args[2]; 508 | dynargs[3] = args[3]; 509 | dynargs[4] = args[4]; 510 | return oraclize_query(datasource, dynargs, gaslimit); 511 | } 512 | 513 | function oraclize_cbAddress() oraclizeAPI internal returns (address){ 514 | return oraclize.cbAddress(); 515 | } 516 | function oraclize_setProof(byte proofP) oraclizeAPI internal { 517 | return oraclize.setProofType(proofP); 518 | } 519 | function oraclize_setCustomGasPrice(uint gasPrice) oraclizeAPI internal { 520 | return oraclize.setCustomGasPrice(gasPrice); 521 | } 522 | 523 | function oraclize_randomDS_getSessionPubKeyHash() oraclizeAPI internal returns (bytes32){ 524 | return oraclize.randomDS_getSessionPubKeyHash(); 525 | } 526 | 527 | function getCodeSize(address _addr) constant internal returns(uint _size) { 528 | assembly { 529 | _size := extcodesize(_addr) 530 | } 531 | } 532 | 533 | function parseAddr(string _a) internal pure returns (address){ 534 | bytes memory tmp = bytes(_a); 535 | uint160 iaddr = 0; 536 | uint160 b1; 537 | uint160 b2; 538 | for (uint i=2; i<2+2*20; i+=2){ 539 | iaddr *= 256; 540 | b1 = uint160(tmp[i]); 541 | b2 = uint160(tmp[i+1]); 542 | if ((b1 >= 97)&&(b1 <= 102)) b1 -= 87; 543 | else if ((b1 >= 65)&&(b1 <= 70)) b1 -= 55; 544 | else if ((b1 >= 48)&&(b1 <= 57)) b1 -= 48; 545 | if ((b2 >= 97)&&(b2 <= 102)) b2 -= 87; 546 | else if ((b2 >= 65)&&(b2 <= 70)) b2 -= 55; 547 | else if ((b2 >= 48)&&(b2 <= 57)) b2 -= 48; 548 | iaddr += (b1*16+b2); 549 | } 550 | return address(iaddr); 551 | } 552 | 553 | function strCompare(string _a, string _b) internal pure returns (int) { 554 | bytes memory a = bytes(_a); 555 | bytes memory b = bytes(_b); 556 | uint minLength = a.length; 557 | if (b.length < minLength) minLength = b.length; 558 | for (uint i = 0; i < minLength; i ++) 559 | if (a[i] < b[i]) 560 | return -1; 561 | else if (a[i] > b[i]) 562 | return 1; 563 | if (a.length < b.length) 564 | return -1; 565 | else if (a.length > b.length) 566 | return 1; 567 | else 568 | return 0; 569 | } 570 | 571 | function indexOf(string _haystack, string _needle) internal pure returns (int) { 572 | bytes memory h = bytes(_haystack); 573 | bytes memory n = bytes(_needle); 574 | if(h.length < 1 || n.length < 1 || (n.length > h.length)) 575 | return -1; 576 | else if(h.length > (2**128 -1)) 577 | return -1; 578 | else 579 | { 580 | uint subindex = 0; 581 | for (uint i = 0; i < h.length; i ++) 582 | { 583 | if (h[i] == n[0]) 584 | { 585 | subindex = 1; 586 | while(subindex < n.length && (i + subindex) < h.length && h[i + subindex] == n[subindex]) 587 | { 588 | subindex++; 589 | } 590 | if(subindex == n.length) 591 | return int(i); 592 | } 593 | } 594 | return -1; 595 | } 596 | } 597 | 598 | function strConcat(string _a, string _b, string _c, string _d, string _e) internal pure returns (string) { 599 | bytes memory _ba = bytes(_a); 600 | bytes memory _bb = bytes(_b); 601 | bytes memory _bc = bytes(_c); 602 | bytes memory _bd = bytes(_d); 603 | bytes memory _be = bytes(_e); 604 | string memory abcde = new string(_ba.length + _bb.length + _bc.length + _bd.length + _be.length); 605 | bytes memory babcde = bytes(abcde); 606 | uint k = 0; 607 | for (uint i = 0; i < _ba.length; i++) babcde[k++] = _ba[i]; 608 | for (i = 0; i < _bb.length; i++) babcde[k++] = _bb[i]; 609 | for (i = 0; i < _bc.length; i++) babcde[k++] = _bc[i]; 610 | for (i = 0; i < _bd.length; i++) babcde[k++] = _bd[i]; 611 | for (i = 0; i < _be.length; i++) babcde[k++] = _be[i]; 612 | return string(babcde); 613 | } 614 | 615 | function strConcat(string _a, string _b, string _c, string _d) internal pure returns (string) { 616 | return strConcat(_a, _b, _c, _d, ""); 617 | } 618 | 619 | function strConcat(string _a, string _b, string _c) internal pure returns (string) { 620 | return strConcat(_a, _b, _c, "", ""); 621 | } 622 | 623 | function strConcat(string _a, string _b) internal pure returns (string) { 624 | return strConcat(_a, _b, "", "", ""); 625 | } 626 | 627 | // parseInt 628 | function parseInt(string _a) internal pure returns (uint) { 629 | return parseInt(_a, 0); 630 | } 631 | 632 | // parseInt(parseFloat*10^_b) 633 | function parseInt(string _a, uint _b) internal pure returns (uint) { 634 | bytes memory bresult = bytes(_a); 635 | uint mint = 0; 636 | bool decimals = false; 637 | for (uint i=0; i= 48)&&(bresult[i] <= 57)){ 639 | if (decimals){ 640 | if (_b == 0) break; 641 | else _b--; 642 | } 643 | mint *= 10; 644 | mint += uint(bresult[i]) - 48; 645 | } else if (bresult[i] == 46) decimals = true; 646 | } 647 | if (_b > 0) mint *= 10**_b; 648 | return mint; 649 | } 650 | 651 | function uint2str(uint i) internal pure returns (string){ 652 | if (i == 0) return "0"; 653 | uint j = i; 654 | uint len; 655 | while (j != 0){ 656 | len++; 657 | j /= 10; 658 | } 659 | bytes memory bstr = new bytes(len); 660 | uint k = len - 1; 661 | while (i != 0){ 662 | bstr[k--] = byte(48 + i % 10); 663 | i /= 10; 664 | } 665 | return string(bstr); 666 | } 667 | 668 | function stra2cbor(string[] arr) internal pure returns (bytes) { 669 | uint arrlen = arr.length; 670 | 671 | // get correct cbor output length 672 | uint outputlen = 0; 673 | bytes[] memory elemArray = new bytes[](arrlen); 674 | for (uint i = 0; i < arrlen; i++) { 675 | elemArray[i] = (bytes(arr[i])); 676 | outputlen += elemArray[i].length + (elemArray[i].length - 1)/23 + 3; //+3 accounts for paired identifier types 677 | } 678 | uint ctr = 0; 679 | uint cborlen = arrlen + 0x80; 680 | outputlen += byte(cborlen).length; 681 | bytes memory res = new bytes(outputlen); 682 | 683 | while (byte(cborlen).length > ctr) { 684 | res[ctr] = byte(cborlen)[ctr]; 685 | ctr++; 686 | } 687 | for (i = 0; i < arrlen; i++) { 688 | res[ctr] = 0x5F; 689 | ctr++; 690 | for (uint x = 0; x < elemArray[i].length; x++) { 691 | // if there's a bug with larger strings, this may be the culprit 692 | if (x % 23 == 0) { 693 | uint elemcborlen = elemArray[i].length - x >= 24 ? 23 : elemArray[i].length - x; 694 | elemcborlen += 0x40; 695 | uint lctr = ctr; 696 | while (byte(elemcborlen).length > ctr - lctr) { 697 | res[ctr] = byte(elemcborlen)[ctr - lctr]; 698 | ctr++; 699 | } 700 | } 701 | res[ctr] = elemArray[i][x]; 702 | ctr++; 703 | } 704 | res[ctr] = 0xFF; 705 | ctr++; 706 | } 707 | return res; 708 | } 709 | 710 | function ba2cbor(bytes[] arr) internal pure returns (bytes) { 711 | uint arrlen = arr.length; 712 | 713 | // get correct cbor output length 714 | uint outputlen = 0; 715 | bytes[] memory elemArray = new bytes[](arrlen); 716 | for (uint i = 0; i < arrlen; i++) { 717 | elemArray[i] = (bytes(arr[i])); 718 | outputlen += elemArray[i].length + (elemArray[i].length - 1)/23 + 3; //+3 accounts for paired identifier types 719 | } 720 | uint ctr = 0; 721 | uint cborlen = arrlen + 0x80; 722 | outputlen += byte(cborlen).length; 723 | bytes memory res = new bytes(outputlen); 724 | 725 | while (byte(cborlen).length > ctr) { 726 | res[ctr] = byte(cborlen)[ctr]; 727 | ctr++; 728 | } 729 | for (i = 0; i < arrlen; i++) { 730 | res[ctr] = 0x5F; 731 | ctr++; 732 | for (uint x = 0; x < elemArray[i].length; x++) { 733 | // if there's a bug with larger strings, this may be the culprit 734 | if (x % 23 == 0) { 735 | uint elemcborlen = elemArray[i].length - x >= 24 ? 23 : elemArray[i].length - x; 736 | elemcborlen += 0x40; 737 | uint lctr = ctr; 738 | while (byte(elemcborlen).length > ctr - lctr) { 739 | res[ctr] = byte(elemcborlen)[ctr - lctr]; 740 | ctr++; 741 | } 742 | } 743 | res[ctr] = elemArray[i][x]; 744 | ctr++; 745 | } 746 | res[ctr] = 0xFF; 747 | ctr++; 748 | } 749 | return res; 750 | } 751 | 752 | 753 | string oraclize_network_name; 754 | function oraclize_setNetworkName(string _network_name) internal { 755 | oraclize_network_name = _network_name; 756 | } 757 | 758 | function oraclize_getNetworkName() internal view returns (string) { 759 | return oraclize_network_name; 760 | } 761 | 762 | function oraclize_newRandomDSQuery(uint _delay, uint _nbytes, uint _customGasLimit) internal returns (bytes32){ 763 | require((_nbytes > 0) && (_nbytes <= 32)); 764 | // Convert from seconds to ledger timer ticks 765 | _delay *= 10; 766 | bytes memory nbytes = new bytes(1); 767 | nbytes[0] = byte(_nbytes); 768 | bytes memory unonce = new bytes(32); 769 | bytes memory sessionKeyHash = new bytes(32); 770 | bytes32 sessionKeyHash_bytes32 = oraclize_randomDS_getSessionPubKeyHash(); 771 | assembly { 772 | mstore(unonce, 0x20) 773 | mstore(add(unonce, 0x20), xor(blockhash(sub(number, 1)), xor(coinbase, timestamp))) 774 | mstore(sessionKeyHash, 0x20) 775 | mstore(add(sessionKeyHash, 0x20), sessionKeyHash_bytes32) 776 | } 777 | bytes memory delay = new bytes(32); 778 | assembly { 779 | mstore(add(delay, 0x20), _delay) 780 | } 781 | 782 | bytes memory delay_bytes8 = new bytes(8); 783 | copyBytes(delay, 24, 8, delay_bytes8, 0); 784 | 785 | bytes[4] memory args = [unonce, nbytes, sessionKeyHash, delay]; 786 | bytes32 queryId = oraclize_query("random", args, _customGasLimit); 787 | 788 | bytes memory delay_bytes8_left = new bytes(8); 789 | 790 | assembly { 791 | let x := mload(add(delay_bytes8, 0x20)) 792 | mstore8(add(delay_bytes8_left, 0x27), div(x, 0x100000000000000000000000000000000000000000000000000000000000000)) 793 | mstore8(add(delay_bytes8_left, 0x26), div(x, 0x1000000000000000000000000000000000000000000000000000000000000)) 794 | mstore8(add(delay_bytes8_left, 0x25), div(x, 0x10000000000000000000000000000000000000000000000000000000000)) 795 | mstore8(add(delay_bytes8_left, 0x24), div(x, 0x100000000000000000000000000000000000000000000000000000000)) 796 | mstore8(add(delay_bytes8_left, 0x23), div(x, 0x1000000000000000000000000000000000000000000000000000000)) 797 | mstore8(add(delay_bytes8_left, 0x22), div(x, 0x10000000000000000000000000000000000000000000000000000)) 798 | mstore8(add(delay_bytes8_left, 0x21), div(x, 0x100000000000000000000000000000000000000000000000000)) 799 | mstore8(add(delay_bytes8_left, 0x20), div(x, 0x1000000000000000000000000000000000000000000000000)) 800 | 801 | } 802 | 803 | oraclize_randomDS_setCommitment(queryId, keccak256(delay_bytes8_left, args[1], sha256(args[0]), args[2])); 804 | return queryId; 805 | } 806 | 807 | function oraclize_randomDS_setCommitment(bytes32 queryId, bytes32 commitment) internal { 808 | oraclize_randomDS_args[queryId] = commitment; 809 | } 810 | 811 | mapping(bytes32=>bytes32) oraclize_randomDS_args; 812 | mapping(bytes32=>bool) oraclize_randomDS_sessionKeysHashVerified; 813 | 814 | function verifySig(bytes32 tosignh, bytes dersig, bytes pubkey) internal returns (bool){ 815 | bool sigok; 816 | address signer; 817 | 818 | bytes32 sigr; 819 | bytes32 sigs; 820 | 821 | bytes memory sigr_ = new bytes(32); 822 | uint offset = 4+(uint(dersig[3]) - 0x20); 823 | sigr_ = copyBytes(dersig, offset, 32, sigr_, 0); 824 | bytes memory sigs_ = new bytes(32); 825 | offset += 32 + 2; 826 | sigs_ = copyBytes(dersig, offset+(uint(dersig[offset-1]) - 0x20), 32, sigs_, 0); 827 | 828 | assembly { 829 | sigr := mload(add(sigr_, 32)) 830 | sigs := mload(add(sigs_, 32)) 831 | } 832 | 833 | 834 | (sigok, signer) = safer_ecrecover(tosignh, 27, sigr, sigs); 835 | if (address(keccak256(pubkey)) == signer) return true; 836 | else { 837 | (sigok, signer) = safer_ecrecover(tosignh, 28, sigr, sigs); 838 | return (address(keccak256(pubkey)) == signer); 839 | } 840 | } 841 | 842 | function oraclize_randomDS_proofVerify__sessionKeyValidity(bytes proof, uint sig2offset) internal returns (bool) { 843 | bool sigok; 844 | 845 | // Step 6: verify the attestation signature, APPKEY1 must sign the sessionKey from the correct ledger app (CODEHASH) 846 | bytes memory sig2 = new bytes(uint(proof[sig2offset+1])+2); 847 | copyBytes(proof, sig2offset, sig2.length, sig2, 0); 848 | 849 | bytes memory appkey1_pubkey = new bytes(64); 850 | copyBytes(proof, 3+1, 64, appkey1_pubkey, 0); 851 | 852 | bytes memory tosign2 = new bytes(1+65+32); 853 | tosign2[0] = byte(1); //role 854 | copyBytes(proof, sig2offset-65, 65, tosign2, 1); 855 | bytes memory CODEHASH = hex"fd94fa71bc0ba10d39d464d0d8f465efeef0a2764e3887fcc9df41ded20f505c"; 856 | copyBytes(CODEHASH, 0, 32, tosign2, 1+65); 857 | sigok = verifySig(sha256(tosign2), sig2, appkey1_pubkey); 858 | 859 | if (sigok == false) return false; 860 | 861 | 862 | // Step 7: verify the APPKEY1 provenance (must be signed by Ledger) 863 | bytes memory LEDGERKEY = hex"7fb956469c5c9b89840d55b43537e66a98dd4811ea0a27224272c2e5622911e8537a2f8e86a46baec82864e98dd01e9ccc2f8bc5dfc9cbe5a91a290498dd96e4"; 864 | 865 | bytes memory tosign3 = new bytes(1+65); 866 | tosign3[0] = 0xFE; 867 | copyBytes(proof, 3, 65, tosign3, 1); 868 | 869 | bytes memory sig3 = new bytes(uint(proof[3+65+1])+2); 870 | copyBytes(proof, 3+65, sig3.length, sig3, 0); 871 | 872 | sigok = verifySig(sha256(tosign3), sig3, LEDGERKEY); 873 | 874 | return sigok; 875 | } 876 | 877 | modifier oraclize_randomDS_proofVerify(bytes32 _queryId, string _result, bytes _proof) { 878 | // Step 1: the prefix has to match 'LP\x01' (Ledger Proof version 1) 879 | require((_proof[0] == "L") && (_proof[1] == "P") && (_proof[2] == 1)); 880 | 881 | bool proofVerified = oraclize_randomDS_proofVerify__main(_proof, _queryId, bytes(_result), oraclize_getNetworkName()); 882 | require(proofVerified); 883 | 884 | _; 885 | } 886 | 887 | function oraclize_randomDS_proofVerify__returnCode(bytes32 _queryId, string _result, bytes _proof) internal returns (uint8){ 888 | // Step 1: the prefix has to match 'LP\x01' (Ledger Proof version 1) 889 | if ((_proof[0] != "L")||(_proof[1] != "P")||(_proof[2] != 1)) return 1; 890 | 891 | bool proofVerified = oraclize_randomDS_proofVerify__main(_proof, _queryId, bytes(_result), oraclize_getNetworkName()); 892 | if (proofVerified == false) return 2; 893 | 894 | return 0; 895 | } 896 | 897 | function matchBytes32Prefix(bytes32 content, bytes prefix, uint n_random_bytes) internal pure returns (bool){ 898 | bool match_ = true; 899 | 900 | require(prefix.length == n_random_bytes); 901 | 902 | for (uint256 i=0; i< n_random_bytes; i++) { 903 | if (content[i] != prefix[i]) match_ = false; 904 | } 905 | 906 | return match_; 907 | } 908 | 909 | function oraclize_randomDS_proofVerify__main(bytes proof, bytes32 queryId, bytes result, string context_name) internal returns (bool){ 910 | 911 | // Step 2: the unique keyhash has to match with the sha256 of (context name + queryId) 912 | uint ledgerProofLength = 3+65+(uint(proof[3+65+1])+2)+32; 913 | bytes memory keyhash = new bytes(32); 914 | copyBytes(proof, ledgerProofLength, 32, keyhash, 0); 915 | if (!(keccak256(keyhash) == keccak256(sha256(context_name, queryId)))) return false; 916 | 917 | bytes memory sig1 = new bytes(uint(proof[ledgerProofLength+(32+8+1+32)+1])+2); 918 | copyBytes(proof, ledgerProofLength+(32+8+1+32), sig1.length, sig1, 0); 919 | 920 | // Step 3: we assume sig1 is valid (it will be verified during step 5) and we verify if 'result' is the prefix of sha256(sig1) 921 | if (!matchBytes32Prefix(sha256(sig1), result, uint(proof[ledgerProofLength+32+8]))) return false; 922 | 923 | // Step 4: commitment match verification, keccak256(delay, nbytes, unonce, sessionKeyHash) == commitment in storage. 924 | // This is to verify that the computed args match with the ones specified in the query. 925 | bytes memory commitmentSlice1 = new bytes(8+1+32); 926 | copyBytes(proof, ledgerProofLength+32, 8+1+32, commitmentSlice1, 0); 927 | 928 | bytes memory sessionPubkey = new bytes(64); 929 | uint sig2offset = ledgerProofLength+32+(8+1+32)+sig1.length+65; 930 | copyBytes(proof, sig2offset-64, 64, sessionPubkey, 0); 931 | 932 | bytes32 sessionPubkeyHash = sha256(sessionPubkey); 933 | if (oraclize_randomDS_args[queryId] == keccak256(commitmentSlice1, sessionPubkeyHash)){ //unonce, nbytes and sessionKeyHash match 934 | delete oraclize_randomDS_args[queryId]; 935 | } else return false; 936 | 937 | 938 | // Step 5: validity verification for sig1 (keyhash and args signed with the sessionKey) 939 | bytes memory tosign1 = new bytes(32+8+1+32); 940 | copyBytes(proof, ledgerProofLength, 32+8+1+32, tosign1, 0); 941 | if (!verifySig(sha256(tosign1), sig1, sessionPubkey)) return false; 942 | 943 | // verify if sessionPubkeyHash was verified already, if not.. let's do it! 944 | if (oraclize_randomDS_sessionKeysHashVerified[sessionPubkeyHash] == false){ 945 | oraclize_randomDS_sessionKeysHashVerified[sessionPubkeyHash] = oraclize_randomDS_proofVerify__sessionKeyValidity(proof, sig2offset); 946 | } 947 | 948 | return oraclize_randomDS_sessionKeysHashVerified[sessionPubkeyHash]; 949 | } 950 | 951 | // the following function has been written by Alex Beregszaszi (@axic), use it under the terms of the MIT license 952 | function copyBytes(bytes from, uint fromOffset, uint length, bytes to, uint toOffset) internal pure returns (bytes) { 953 | uint minLength = length + toOffset; 954 | 955 | // Buffer too small 956 | require(to.length >= minLength); // Should be a better way? 957 | 958 | // NOTE: the offset 32 is added to skip the `size` field of both bytes variables 959 | uint i = 32 + fromOffset; 960 | uint j = 32 + toOffset; 961 | 962 | while (i < (32 + fromOffset + length)) { 963 | assembly { 964 | let tmp := mload(add(from, i)) 965 | mstore(add(to, j), tmp) 966 | } 967 | i += 32; 968 | j += 32; 969 | } 970 | 971 | return to; 972 | } 973 | 974 | // the following function has been written by Alex Beregszaszi (@axic), use it under the terms of the MIT license 975 | // Duplicate Solidity's ecrecover, but catching the CALL return value 976 | function safer_ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal returns (bool, address) { 977 | // We do our own memory management here. Solidity uses memory offset 978 | // 0x40 to store the current end of memory. We write past it (as 979 | // writes are memory extensions), but don't update the offset so 980 | // Solidity will reuse it. The memory used here is only needed for 981 | // this context. 982 | 983 | // FIXME: inline assembly can't access return values 984 | bool ret; 985 | address addr; 986 | 987 | assembly { 988 | let size := mload(0x40) 989 | mstore(size, hash) 990 | mstore(add(size, 32), v) 991 | mstore(add(size, 64), r) 992 | mstore(add(size, 96), s) 993 | 994 | // NOTE: we can reuse the request memory because we deal with 995 | // the return code 996 | ret := call(3000, 1, 0, size, 128, size, 32) 997 | addr := mload(size) 998 | } 999 | 1000 | return (ret, addr); 1001 | } 1002 | 1003 | // the following function has been written by Alex Beregszaszi (@axic), use it under the terms of the MIT license 1004 | function ecrecovery(bytes32 hash, bytes sig) internal returns (bool, address) { 1005 | bytes32 r; 1006 | bytes32 s; 1007 | uint8 v; 1008 | 1009 | if (sig.length != 65) 1010 | return (false, 0); 1011 | 1012 | // The signature format is a compact form of: 1013 | // {bytes32 r}{bytes32 s}{uint8 v} 1014 | // Compact means, uint8 is not padded to 32 bytes. 1015 | assembly { 1016 | r := mload(add(sig, 32)) 1017 | s := mload(add(sig, 64)) 1018 | 1019 | // Here we are loading the last 32 bytes. We exploit the fact that 1020 | // 'mload' will pad with zeroes if we overread. 1021 | // There is no 'mload8' to do this, but that would be nicer. 1022 | v := byte(0, mload(add(sig, 96))) 1023 | 1024 | // Alternative solution: 1025 | // 'byte' is not working due to the Solidity parser, so lets 1026 | // use the second best option, 'and' 1027 | // v := and(mload(add(sig, 65)), 255) 1028 | } 1029 | 1030 | // albeit non-transactional signatures are not specified by the YP, one would expect it 1031 | // to match the YP range of [27, 28] 1032 | // 1033 | // geth uses [0, 1] and some clients have followed. This might change, see: 1034 | // https://github.com/ethereum/go-ethereum/issues/2053 1035 | if (v < 27) 1036 | v += 27; 1037 | 1038 | if (v != 27 && v != 28) 1039 | return (false, 0); 1040 | 1041 | return safer_ecrecover(hash, v, r, s); 1042 | } 1043 | 1044 | } 1045 | // --------------------------------------------------------------------------------