Skip to content

Instantly share code, notes, and snippets.

@dryruner
Last active August 22, 2020 23:58
Show Gist options
  • Save dryruner/3b2ca0410af407497bdc70ffe79ee123 to your computer and use it in GitHub Desktop.
Save dryruner/3b2ca0410af407497bdc70ffe79ee123 to your computer and use it in GitHub Desktop.
DOSRandom() example
/**
*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