Last active
August 22, 2020 23:58
-
-
Save dryruner/3b2ca0410af407497bdc70ffe79ee123 to your computer and use it in GitHub Desktop.
DOSRandom() example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
*Submitted for verification at Etherscan.io on 2020-08-22 | |
*/ | |
pragma solidity ^0.5.0; | |
/** | |
* @title Ownable | |
* @dev The Ownable contract has an owner address, and provides basic authorization control | |
* functions, this simplifies the implementation of "user permissions". | |
*/ | |
contract Ownable { | |
address private _owner; | |
event OwnershipRenounced(address indexed previousOwner); | |
event OwnershipTransferred( | |
address indexed previousOwner, | |
address indexed newOwner | |
); | |
/** | |
* @dev The Ownable constructor sets the original `owner` of the contract to the sender | |
* account. | |
*/ | |
constructor() public { | |
_owner = msg.sender; | |
} | |
/** | |
* @return the address of the owner. | |
*/ | |
function owner() public view returns(address) { | |
return _owner; | |
} | |
/** | |
* @dev Throws if called by any account other than the owner. | |
*/ | |
modifier onlyOwner() { | |
require(isOwner()); | |
_; | |
} | |
/** | |
* @return true if `msg.sender` is the owner of the contract. | |
*/ | |
function isOwner() public view returns(bool) { | |
return msg.sender == _owner; | |
} | |
/** | |
* @dev Allows the current owner to relinquish control of the contract. | |
* @notice Renouncing to ownership will leave the contract without an owner. | |
* It will not be possible to call the functions with the `onlyOwner` | |
* modifier anymore. | |
*/ | |
function renounceOwnership() public onlyOwner { | |
emit OwnershipRenounced(_owner); | |
_owner = address(0); | |
} | |
/** | |
* @dev Allows the current owner to transfer control of the contract to a newOwner. | |
* @param newOwner The address to transfer ownership to. | |
*/ | |
function transferOwnership(address newOwner) public onlyOwner { | |
_transferOwnership(newOwner); | |
} | |
/** | |
* @dev Transfers control of the contract to a newOwner. | |
* @param newOwner The address to transfer ownership to. | |
*/ | |
function _transferOwnership(address newOwner) internal { | |
require(newOwner != address(0)); | |
emit OwnershipTransferred(_owner, newOwner); | |
_owner = newOwner; | |
} | |
} | |
contract DOSProxyInterface { | |
function query(address, uint, string memory, string memory) public returns (uint); | |
function requestRandom(address, uint) public returns (uint); | |
} | |
contract DOSPaymentInterface { | |
function setPaymentMethod(address payer, address tokenAddr) public; | |
function defaultTokenAddr() public returns(address); | |
} | |
contract DOSAddressBridgeInterface { | |
function getProxyAddress() public view returns (address); | |
function getPaymentAddress() public view returns (address); | |
} | |
contract ERC20I { | |
function balanceOf(address who) public view returns (uint); | |
function transfer(address to, uint value) public returns (bool); | |
function approve(address spender, uint value) public returns (bool); | |
} | |
contract DOSOnChainSDK is Ownable { | |
// Comment out utils library if you don't need it to save gas. (L4 and L30) | |
// using utils for *; | |
DOSProxyInterface dosProxy; | |
DOSAddressBridgeInterface dosAddrBridge = | |
DOSAddressBridgeInterface(0xeE2e9f35c9F91571535173902E7e7B4E67deE32b); | |
modifier resolveAddress { | |
dosProxy = DOSProxyInterface(dosAddrBridge.getProxyAddress()); | |
_; | |
} | |
modifier auth { | |
// Filter out malicious __callback__ caller. | |
require(msg.sender == dosAddrBridge.getProxyAddress(), "Unauthenticated response"); | |
_; | |
} | |
// @dev: call setup function and transfer DOS tokens into deployed contract as oracle fees. | |
function setup() internal onlyOwner { | |
address paymentAddr = dosAddrBridge.getPaymentAddress(); | |
address defaultToken = DOSPaymentInterface(dosAddrBridge.getPaymentAddress()).defaultTokenAddr(); | |
ERC20I(defaultToken).approve(paymentAddr, uint(-1)); | |
DOSPaymentInterface(dosAddrBridge.getPaymentAddress()).setPaymentMethod(address(this), defaultToken); | |
} | |
// @dev: refund all unused fees to caller. | |
function refund() public onlyOwner { | |
address token = DOSPaymentInterface(dosAddrBridge.getPaymentAddress()).defaultTokenAddr(); | |
uint amount = ERC20I(token).balanceOf(address(this)); | |
ERC20I(token).transfer(msg.sender, amount); | |
} | |
// @dev: Call this function to get a unique queryId to differentiate | |
// parallel requests. A return value of 0x0 stands for error and a | |
// related event would be emitted. | |
// @timeout: Estimated timeout in seconds specified by caller; e.g. 15. | |
// Response is not guaranteed if processing time exceeds this. | |
// @dataSource: Data source destination specified by caller. | |
// E.g.: 'https://api.coinbase.com/v2/prices/ETH-USD/spot' | |
// @selector: A selector expression provided by caller to filter out | |
// specific data fields out of the raw response. The response | |
// data format (json, xml/html, and more) is identified from | |
// the selector expression. | |
// E.g. Use "$.data.amount" to extract "194.22" out. | |
// { | |
// "data":{ | |
// "base":"ETH", | |
// "currency":"USD", | |
// "amount":"194.22" | |
// } | |
// } | |
// Check below documentation for details. | |
// (https://dosnetwork.github.io/docs/#/contents/blockchains/ethereum?id=selector). | |
function DOSQuery(uint timeout, string memory dataSource, string memory selector) | |
internal | |
resolveAddress | |
returns (uint) | |
{ | |
return dosProxy.query(address(this), timeout, dataSource, selector); | |
} | |
// @dev: Must override __callback__ to process a corresponding response. A | |
// user-defined event could be added to notify the Dapp frontend that | |
// the response is ready. | |
// @queryId: A unique queryId returned by DOSQuery() for callers to | |
// differentiate parallel responses. | |
// @result: Response for the specified queryId. | |
function __callback__(uint queryId, bytes calldata result) external { | |
// To be overridden in the caller contract. | |
} | |
// @dev: Call this function to request either a fast but insecure random | |
// number or a safe and secure random number delivered back | |
// asynchronously through the __callback__ function. | |
// Depending on the mode, the return value would be a random number | |
// (for fast mode) or a requestId (for safe mode). | |
// @seed: Optional random seed provided by caller. | |
function DOSRandom(uint seed) | |
internal | |
resolveAddress | |
returns (uint) | |
{ | |
return dosProxy.requestRandom(address(this), seed); | |
} | |
// @dev: Must override __callback__ to process a corresponding random | |
// number. A user-defined event could be added to notify the Dapp | |
// frontend that a new secure random number is generated. | |
// @requestId: A unique requestId returned by DOSRandom() for requester to | |
// differentiate random numbers generated concurrently. | |
// @generatedRandom: Generated secure random number for the specific | |
// requestId. | |
function __callback__(uint requestId, uint generatedRandom) external auth { | |
// To be overridden in the caller contract. | |
} | |
} | |
contract SimpleDice is DOSOnChainSDK { | |
address payable public devAddress; | |
uint public devContributed = 0; | |
// 1% winning payout goes to developer account | |
uint public developCut = 1; | |
// precise to 4 digits after decimal point. | |
uint public decimal = 4; | |
// gameId => gameInfo | |
mapping(uint => DiceInfo) public games; | |
struct DiceInfo { | |
uint rollUnder; // betted number, player wins if random < rollUnder | |
uint amountBet; // amount in wei | |
address payable player; // better address | |
} | |
event ReceivedBet( | |
uint gameId, | |
uint rollUnder, | |
uint weiBetted, | |
address better | |
); | |
event PlayerWin(uint gameId, uint generated, uint betted, uint amountWin); | |
event PlayerLose(uint gameId, uint generated, uint betted); | |
constructor () public { | |
setup(); | |
// Convert from address to payable address. | |
devAddress = address(uint160(owner())); | |
} | |
function min(uint a, uint b) internal pure returns(uint) { | |
return a < b ? a : b; | |
} | |
// Only receive bankroll funding from developer. | |
function() external payable onlyOwner { | |
devContributed += msg.value; | |
} | |
// Only developer can withdraw the amount up to what he has contributed. | |
function devWithdrawal() public onlyOwner { | |
uint withdrawalAmount = min(address(this).balance, devContributed); | |
devContributed = 0; | |
devAddress.transfer(withdrawalAmount); | |
} | |
// 100 / (rollUnder - 1) * (1 - 0.01) => 99 / (rollUnder - 1) | |
// Not using SafeMath as this function cannot overflow anyway. | |
function computeWinPayout(uint rollUnder) public view returns(uint) { | |
return 99 * (10 ** decimal) / (rollUnder - 1); | |
} | |
// 100 / (rollUnder - 1) * 0.01 | |
function computeDeveloperCut(uint rollUnder) public view returns(uint) { | |
return 10 ** decimal / (rollUnder - 1); | |
} | |
function play(uint rollUnder) public payable { | |
// winChance within [1%, 95%] | |
require(rollUnder >= 2 && rollUnder <= 96, "rollUnder should be in 2~96"); | |
// Make sure contract has enough balance to cover payouts before game. | |
// Not using SafeMath as I'm not expecting this demo contract's | |
// balance to be very large. | |
require(address(this).balance * (10 ** decimal) >= msg.value * computeWinPayout(rollUnder), | |
"Game contract doesn't have enough balance, decrease rollUnder"); | |
// Request a safe, unmanipulatable random number from DOS Network with | |
// optional seed. | |
uint gameId = DOSRandom(now); | |
games[gameId] = DiceInfo(rollUnder, msg.value, msg.sender); | |
// Emit event to notify Dapp frontend | |
emit ReceivedBet(gameId, rollUnder, msg.value, msg.sender); | |
} | |
function __callback__(uint requestId, uint generatedRandom) external auth { | |
address payable player = games[requestId].player; | |
require(player != address(0x0)); | |
uint gen_rnd = generatedRandom % 100 + 1; | |
uint rollUnder = games[requestId].rollUnder; | |
uint betted = games[requestId].amountBet; | |
delete games[requestId]; | |
if (gen_rnd < rollUnder) { | |
// Player wins | |
uint payout = betted * computeWinPayout(rollUnder) / (10 ** decimal); | |
uint devPayout = betted * computeDeveloperCut(rollUnder) / (10 ** decimal); | |
emit PlayerWin(requestId, gen_rnd, rollUnder, payout); | |
player.transfer(payout); | |
devAddress.transfer(devPayout); | |
} else { | |
// Lose | |
emit PlayerLose(requestId, gen_rnd, rollUnder); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment