Last active
April 10, 2018 14:33
-
-
Save huahuayu/06aa6557594226a9c90b675fef6a3c94 to your computer and use it in GitHub Desktop.
etherindex
This file contains 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
pragma solidity ^0.4.21; | |
import "github.com/oraclize/ethereum-api/oraclizeAPI.sol"; | |
contract SafeMath { | |
function safeToAdd(uint a, uint b) pure internal returns (bool) { | |
return (a + b >= a); | |
} | |
function safeAdd(uint a, uint b) pure internal returns (uint) { | |
require(safeToAdd(a, b)); | |
return (a + b); | |
} | |
function safeToSubtract(uint a, uint b) pure internal returns (bool) { | |
return (b <= a); | |
} | |
function safeSub(uint a, uint b) pure internal returns (uint) { | |
require(safeToSubtract(a, b)); | |
return (a - b); | |
} | |
} | |
/** | |
* etherindex contract | |
*/ | |
contract Etherindex is usingOraclize, SafeMath { | |
/* | |
* 检查投注的合法性: | |
* 1. 投注金额 > 0 todo:目前只检查了这一个条件 | |
* 2. 目标收益 > 0 | |
* 3. 最大收益 > 目标收益 | |
* 4. 投注日期合法 | |
* 5. 预测区间上限 > 下限 | |
* 6. 预测区间下限 > 0 | |
*/ | |
modifier betIsValid(uint _userBetValue) { | |
require(_userBetValue > 0); | |
_; | |
} | |
/* | |
* checks game is currently active | |
*/ | |
modifier systemIsActive { | |
require(gamePaused == false); | |
_; | |
} | |
/* | |
* checks payouts are currently active | |
*/ | |
modifier payoutsAreActive { | |
require(payoutsPaused == false); | |
_; | |
} | |
/* | |
* checks only Oraclize address is calling | |
*/ | |
modifier onlyOraclize { | |
require(msg.sender == oraclize_cbAddress()); | |
_; | |
} | |
/* | |
* checks only owner address is calling | |
*/ | |
modifier onlyOwner { | |
require(msg.sender == owner); | |
_; | |
} | |
/* | |
* checks only treasury address is calling | |
*/ | |
modifier onlyTreasury { | |
require(msg.sender == treasury); | |
_; | |
} | |
/* | |
* system vars | |
*/ | |
uint public minBet = 100000000000000000; // 最小投注0.1个ether todo: 待确认 | |
uint public maxBet = 10000000000000000000; // 最大投注不可超过10 ether todo: 待确认 | |
uint public maxProfit = 20000000000000000000; // 玩家最大目标收益不可超过20个ether todo: 待确认 | |
bool public gamePaused; // true - 游戏暂停,false - 游戏未暂停 | |
uint32 public gasForOraclize; // 给oraclize回调方法的费用 | |
address public owner; // contract owner address | |
address public treasury; // 财务账户address | |
bool public payoutsPaused; // true - 付款暂停,fasle - 付款未暂停 | |
uint public maxPendingPayouts; // 最大待付金额 | |
uint16 public timeout; // 支付约束时间 | |
uint public totalBets = 244612; // 初始化合约中断数据 - 总投注次数 | |
uint public totalWeiWon = 110633844560463069959901; // 初始化合约中断数据 - 用户赢取的总数 | |
uint public totalWeiWagered = 316486087709317593009320; // 初始化合约中断数据 - 总投注金额 | |
uint8 constant decimalPoint = 3; // 精确三位小数 | |
/* | |
* oraclize vars | |
*/ | |
// query type constant | |
uint8 constant VALIDATECHECK = 0; | |
uint8 constant INQUERYPRICE = 1; | |
mapping (bytes32 => uint8) public queryType; // 将queryId和query type绑定 | |
mapping(string => mapping(uint => uint)) closePrices; // 实际收盘价 string代表指数名,一个uint代表收盘日期 | |
mapping(bytes32 => indexStruct) indexs; // | |
struct indexStruct { | |
string indexName; | |
uint indexDate; | |
} | |
/* | |
* user vars | |
*/ | |
//投注状态 INITIAL-初始状态(还未通过有效性检查), PENDING-待开奖, INVALID-非法输入, LOSE-失败, WIN-成功, WIN_FAILED_SEND-成功但转账失败, REFUND-系统或网络原因退款, REFUND_FAILED_SEND-系统或网络原因退款但退款失败 | |
uint8 constant INITIAL = 0; | |
uint8 constant PENDING = 1; | |
uint8 constant INVALID = 2; | |
uint8 constant LOSE = 3; | |
uint8 constant WIN = 4; | |
uint8 constant WIN_FAILED_SEND = 5; | |
uint8 constant REFUND = 6; | |
uint8 constant REFUND_FAILED_SEND = 7; | |
address[] addrArray; //用户地址数组 todo: 暂时未用上 | |
uint breakPoint; // 断点 todo: check later | |
mapping (bytes32 => address) public userAddress; // 用户地址 | |
mapping (bytes32 => address) public userTempAddress; // 暂存用户地址,防止重入攻击 | |
mapping (bytes32 => bytes32) public userBetId; // 用户投注id | |
mapping (bytes32 => uint) public userBetValue; // 用户投注金额 | |
mapping (bytes32 => uint) public userTempBetValue; // 暂存投注金额,防止重入攻击 | |
mapping (bytes32 => uint) public userProfit; // 用户目标收益 | |
mapping (bytes32 => uint) public userTempProfit; // 暂存用户目标收益,防止重入攻击 | |
mapping (bytes32 => uint) public userMaxProfitLimit; // 本次投注允许的最大投注金额 | |
mapping (bytes32 => string) public userUnderlyingIndex; // 用户选择的标的指数 | |
mapping (bytes32 => uint) public userBetDate; // 用户选择的标的指数收盘日期 | |
mapping (bytes32 => uint) public userIntervalLow; // 用户投注区间下限 | |
mapping (bytes32 => uint) public userIntervalHigh; // 用户投注区间上限 | |
mapping (bytes32 => uint) public userBetTimestamp; // 用户投注时间戳 in UTC | |
mapping (bytes32 => uint8) public userBetStatus; // 投注状态 | |
mapping (address => uint) public userPendingWithdrawals; // 待支付给用户的金额 | |
/* | |
* events | |
*/ | |
/* log bets + output to web3 for precise 'payout on win' field in UI */ | |
event LogBet(bytes32 indexed _userBetId, address indexed _userAddress, string _userUnderlyingIndex, uint _userRewardValue, uint _userProfit, uint _userBetValue, string _userIntervalLow, string _userIntervalhigh, uint8 _betStatus, uint _userBetTimestamp, uint _systemTimestamp); | |
/* output to web3 UI on bet result*/ | |
// Status: 0-INITIAL, 1-PENDING, 2-INVALID, 3-LOSE, 4-WIN, 5-WIN_FAILED_SEND, 6-REFUND, 7-REFUND_FAILED_SEND | |
event LogResult(bytes32 indexed _userBetId, address indexed _userAddress, string _userUnderlyingIndex, uint _userRewardValue, uint _userProfit, uint _userBetValue, uint _userIntervalLow, uint _userIntervalhigh, uint8 _betStatus, uint _timestamp); | |
/* log manual refunds */ | |
event LogRefund(bytes32 indexed _userBetId, address indexed _userAddress, uint indexed RefundValue, uint _timestamp); | |
/* log owner transfers */ | |
event LogOwnerTransfer(address indexed SentToAddress, uint indexed AmountTransferred, uint _timestamp); | |
/* owner fund contract */ | |
event LogOwnerFundContract(address _ownerAddress, uint _currentContractBalance, uint _fundAmount, uint _timestamp); | |
/* log index price on specific date */ | |
event logIndexPrice(bytes32 _queryId, string _indexName, uint _indexDate, string _closePrice, bytes _proof, uint _timestamp); | |
//debug @test | |
event logUint8(string item, uint8 variable); | |
event logUint16(string item, uint16 variable); | |
event logUint32(string item, uint32 variable); | |
event logUint64(string item, uint64 variable); | |
event logUint128(string item, uint128 variable); | |
event logUint256(string item, uint256 variable); | |
event logUint(string item, uint variable); | |
event logInt(string item, int variable); | |
event logBool(string item, bool variable); | |
event logBytes(string item, bytes variable); | |
event logBytes32(string item, bytes32 variable); | |
event logAddress(string item, address variable); | |
event logString(string item, string variable); | |
event trace(string toDisplay); | |
/* | |
* init | |
*/ | |
function Etherindex() public { | |
owner = msg.sender; | |
treasury = msg.sender; | |
oraclize_setNetwork(networkID_auto); | |
/* use TLSNotary for oraclize call */ | |
oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS); | |
/* todo:init gas for oraclize */ | |
gasForOraclize = 935000000; | |
/* todo:init gas price for callback (default 20 gwei)*/ | |
oraclize_setCustomGasPrice(200000000000 wei); | |
/* default timeout setting */ | |
timeout = 300; | |
} | |
/* | |
* public function | |
* user submit bet | |
* only if game is active & bet is valid can query oraclize and set user vars | |
*/ | |
function userBet(string _userUnderlyingIndex, uint _userBetDate, string _userIntervalLow, string _userIntervalHigh, uint _userProfit, uint _userMaxProfitLimit, uint _userBetTimestamp) public | |
payable | |
systemIsActive | |
{ | |
// 输入检查: | |
// todo: 订单到链上的时间 - 投注的时间 > timeout时间(默认300秒,owner可设定)则认为支付超时、订单做退款处理 | |
//require((now - _userBetTimestamp) < timeout); | |
// 投注金额 > 0 | |
require(msg.value > 0); | |
// 用户目标收益 < 本次投注允许的最大投注金额 | |
require(_userProfit < _userMaxProfitLimit); | |
// 用户目标收益 > 0 | |
require(_userProfit > 0); | |
// 投注日期合法 todo: 目前仅检查投注日期是否大于0 | |
require(_userBetDate > 0); | |
// 将预测区间转换为uint格式,且将小数点右移3位 | |
uint userIntervalLowUint = parseInt(_userIntervalLow,decimalPoint); | |
uint userIntervalHighUint = parseInt(_userIntervalHigh,decimalPoint); | |
// 用户投注区间下限 > 0 | |
require(userIntervalLowUint > 0); | |
// 用户投注区间上限 > 用户投注区间下限 | |
require(userIntervalHighUint > userIntervalLowUint); | |
// 合约余额 > 潜在待赔付金额 | |
require(address(this).balance > safeAdd(safeAdd(msg.value, _userProfit), maxPendingPayouts)); | |
// todo: api有待确认 | |
bytes32 queryId = oraclize_query("URL", "https://api.iextrading.com/1.0/stock/aapl/price"); | |
queryType[queryId] = VALIDATECHECK; | |
// @test | |
emit logBytes32("queryId",queryId); | |
// 将用户信息和betId关联 | |
userAddress[queryId] = msg.sender; | |
addrArray.push(msg.sender); | |
userBetId[queryId] = queryId; | |
userBetValue[queryId] = msg.value; | |
userProfit[queryId] = _userProfit; | |
userMaxProfitLimit[queryId] = _userMaxProfitLimit; | |
userUnderlyingIndex[queryId] = _userUnderlyingIndex; | |
userBetDate[queryId] = _userBetDate; | |
userIntervalLow[queryId] = userIntervalLowUint; | |
userIntervalHigh[queryId] = userIntervalHighUint; | |
userBetTimestamp[queryId] = _userBetTimestamp; | |
// bet状态为初始状态 | |
userBetStatus[queryId] = INITIAL; | |
// 记录投注信息 | |
emit LogBet(queryId, msg.sender, _userUnderlyingIndex, safeAdd(msg.value, _userProfit), _userProfit, msg.value, _userIntervalLow, _userIntervalHigh, userBetStatus[queryId], _userBetTimestamp, now); | |
} | |
// call it by oraclizeWork("inqueryWork",1111), | |
// verfiy oraclize callback result by getClosePrice("inqueryWork",1111), it will have value | |
function oraclizeWork(string _indexName, uint _date) public payable onlyOwner returns(bool) | |
{ | |
if (closePrices[_indexName][_date] == 0){ | |
bytes32 queryId = oraclize_query("URL", "https://api.iextrading.com/1.0/stock/aapl/price"); | |
emit logBytes32("queryId",queryId); | |
queryType[queryId] = INQUERYPRICE; | |
indexs[queryId].indexName = _indexName; | |
indexs[queryId].indexDate = _date; | |
return true; | |
} | |
else | |
return false; | |
} | |
// call it by oraclizeNotWork("inqueryNotWork",2222), | |
// verfiy oraclize callback result by getClosePrice("inqueryNotWork",2222), it don't log value, returns zero(it suppose to be 24245.13) | |
function oraclizeNotWork(string _indexName, uint _date) public payable onlyOwner returns(bool) | |
{ | |
if (closePrices[_indexName][_date] == 0){ | |
bytes32 queryId = oraclize_query("URL", "http://119.28.70.201:8792/getprice/?index=DJI&date=17623"); // the only difference from inqueryWork is this URL | |
emit logBytes32("queryId",queryId); | |
queryType[queryId] = INQUERYPRICE; | |
indexs[queryId].indexName = _indexName; | |
indexs[queryId].indexDate = _date; | |
return true; | |
} | |
else | |
return false; | |
} | |
function __callback(bytes32 myid, string result, bytes proof) public | |
onlyOraclize | |
payoutsAreActive | |
{ | |
//@test | |
emit logBytes32("callback myid",myid); | |
emit logString("result",result); | |
emit logBytes("proof",proof); | |
// __callback的调用必须源自VALIDATECHECK, PAYOUTCHECK | |
require (queryType[myid] != VALIDATECHECK || queryType[myid] != INQUERYPRICE); | |
if (queryType[myid] == VALIDATECHECK){ | |
require (userAddress[myid] != 0x0); | |
// todo: validate_check() and update bet status | |
/* safely increase maxPendingPayouts liability - calc all pending payouts under assumption they win */ | |
maxPendingPayouts = safeAdd(maxPendingPayouts, safeAdd(userBetValue[myid], userProfit[myid])); | |
/* check contract can payout on win */ | |
require (maxPendingPayouts < address(this).balance); | |
// after validate check update userBetStatus to PENDING | |
userBetStatus[myid] = PENDING; | |
totalBets += 1; | |
totalWeiWagered += userBetValue[myid]; | |
emit trace("totalBets updated"); | |
return; | |
} | |
if (queryType[myid] == INQUERYPRICE){ | |
require (bytes(result).length != 0 && bytes(proof).length != 0 && parseInt(result,decimalPoint) != 0); | |
closePrices[indexs[myid].indexName][indexs[myid].indexDate] = parseInt(result,decimalPoint); | |
emit logIndexPrice(myid, indexs[myid].indexName, indexs[myid].indexDate, result, proof, now); | |
return; | |
} | |
} | |
function payout(bytes32 myid) public onlyOwner{ | |
//@test | |
userBetId[myid] = myid; | |
userUnderlyingIndex[myid] = "HSI"; | |
userBetDate[myid] = 17625; | |
closePrices[userUnderlyingIndex[myid]][userBetDate[myid]] = 3023118; | |
userAddress[myid] = 0x66322f87f99F5e054E7309da36d20889c6D43728; | |
userProfit[myid] = 200000000000000000; //0.2 Ether | |
maxPendingPayouts = 10000000000000000000; //10 ether | |
userBetValue[myid] = 1000000000000000000; //1 ether | |
userBetStatus[myid] = 1; | |
userIntervalLow[myid] = 3010023; | |
userIntervalHigh[myid] = 3030043; | |
emit logBytes32("myid",myid); | |
emit logString("underlying index",userUnderlyingIndex[myid]); | |
emit logUint("user bet date",userBetDate[myid]); | |
emit logUint("close price",closePrices[userUnderlyingIndex[myid]][userBetDate[myid]]); | |
/* get the userAddress for this query id */ | |
userTempAddress[myid] = userAddress[myid]; | |
/* delete userAddress for this query id */ | |
delete userAddress[myid]; | |
/* map the userProfit for this query id */ | |
userTempProfit[myid] = userProfit[myid]; | |
/* set userProfit for this query id to 0 */ | |
userProfit[myid] = 0; | |
/* safely reduce maxPendingPayouts liability */ | |
maxPendingPayouts = safeSub(maxPendingPayouts, userTempProfit[myid]); | |
/* map the userBetValue for this query id */ | |
userTempBetValue[myid] = userBetValue[myid]; | |
/* set userBetValue for this query id to 0 */ | |
userBetValue[myid] = 0; | |
uint userTempReward = safeAdd(userTempBetValue[myid], userTempProfit[myid]); | |
/* | |
* refund | |
* if result is 0 result is empty or no proof refund original bet value | |
* if refund fails save refund value to userPendingWithdrawals | |
*/ | |
if(closePrices[userUnderlyingIndex[myid]][userBetDate[myid]] == 0){ | |
userBetStatus[myid] = REFUND; | |
/* | |
* send refund - external call to an untrusted contract | |
* if send fails map refund value to userPendingWithdrawals[address] | |
* for withdrawal later via userWithdrawPendingTransactions | |
*/ | |
if(!userTempAddress[myid].send(userTempBetValue[myid])){ | |
userBetStatus[myid] = REFUND_FAILED_SEND; | |
/* if send failed let user withdraw via userWithdrawPendingTransactions */ | |
userPendingWithdrawals[userTempAddress[myid]] = safeAdd(userPendingWithdrawals[userTempAddress[myid]], userTempBetValue[myid]); | |
} | |
emit LogResult(userBetId[myid], userTempAddress[myid], userUnderlyingIndex[myid], safeAdd(userTempBetValue[myid], userTempProfit[myid]), userTempProfit[myid], userTempBetValue[myid], userIntervalLow[myid], userIntervalHigh[myid], userBetStatus[myid], now); | |
return; | |
} | |
/* | |
* pay winner | |
* update contract balance to calculate new max bet | |
* send reward | |
* if send of reward fails save value to userPendingWithdrawals | |
*/ | |
if(userIntervalLow[myid] <= closePrices[userUnderlyingIndex[myid]][userBetDate[myid]] && userIntervalHigh[myid] >= closePrices[userUnderlyingIndex[myid]][userBetDate[myid]]){ | |
/* update total wei won */ | |
totalWeiWon = safeAdd(totalWeiWon, userTempProfit[myid]); | |
// set status to WIN | |
userBetStatus[myid] = WIN; | |
/* | |
* send win - external call to an untrusted contract | |
* if send fails map reward value to userPendingWithdrawals[address] | |
* for withdrawal later via userWithdrawPendingTransactions | |
*/ | |
if(!userTempAddress[myid].send(userTempReward)){ | |
// set status to WIN_FAILED_SEND | |
userBetStatus[myid] = WIN_FAILED_SEND; | |
/* if send failed let user withdraw via userWithdrawPendingTransactions */ | |
userPendingWithdrawals[userTempAddress[myid]] = safeAdd(userPendingWithdrawals[userTempAddress[myid]], userTempReward); | |
} | |
} | |
else { | |
/* | |
* no win | |
* send 1 wei to a losing bet | |
*/ | |
// set status to WIN_FAILED_SEND | |
userBetStatus[myid] = LOSE; | |
if(!userTempAddress[myid].send(1)){ | |
/* if send failed let user withdraw via userWithdrawPendingTransactions */ | |
userPendingWithdrawals[userTempAddress[myid]] = safeAdd(userPendingWithdrawals[userTempAddress[myid]], 1); | |
} | |
} | |
emit LogResult(userBetId[myid], userTempAddress[myid], userUnderlyingIndex[myid], userTempReward, userTempProfit[myid], userTempBetValue[myid], userIntervalLow[myid], userIntervalHigh[myid], userBetStatus[myid], now); | |
return; | |
} | |
/* | |
* public function | |
* in case of a failed refund or win send | |
*/ | |
function userWithdrawPendingTransactions() public | |
payoutsAreActive | |
returns (bool) | |
{ | |
uint withdrawAmount = userPendingWithdrawals[msg.sender]; | |
userPendingWithdrawals[msg.sender] = 0; | |
/* external call to untrusted contract */ | |
if (msg.sender.call.value(withdrawAmount)()) { | |
return true; | |
} else { | |
/* if send failed revert userPendingWithdrawals[msg.sender] = 0; */ | |
/* user can try to withdraw again later */ | |
userPendingWithdrawals[msg.sender] = withdrawAmount; | |
return false; | |
} | |
} | |
/* check for pending withdrawals */ | |
function userGetPendingTxByAddress(address addressToCheck) public constant returns (uint) { | |
return userPendingWithdrawals[addressToCheck]; | |
} | |
/* | |
* owner/treasury address only functions | |
*/ | |
function () | |
payable | |
onlyTreasury | |
{ | |
emit LogOwnerFundContract(msg.sender, address(this).balance, msg.value, now); | |
} | |
/* set gas price for oraclize callback */ | |
function ownerSetCallbackGasPrice(uint newCallbackGasPrice) public | |
onlyOwner | |
{ | |
oraclize_setCustomGasPrice(newCallbackGasPrice); | |
} | |
/* set gas limit for oraclize query */ | |
function ownerSetOraclizeSafeGas(uint32 newSafeGasToOraclize) public | |
onlyOwner | |
{ | |
gasForOraclize = newSafeGasToOraclize; | |
} | |
/* only owner address can set minBet */ | |
function ownerSetMinBet(uint newMinimumBet) public | |
onlyOwner | |
{ | |
minBet = newMinimumBet; | |
} | |
/* only owner address can transfer ether */ | |
function ownerTransferEther(address sendTo, uint amount) public | |
onlyOwner | |
{ | |
sendTo.transfer(amount); | |
emit LogOwnerTransfer(sendTo, amount,now); | |
} | |
/* only owner address can do manual refund | |
* used only if bet placed but not execute payout method after stock market close | |
* filter LogBet by address and/or userBetId, do manual refund only when meet below conditions: | |
* 1. record should in logBet; | |
* 2. record should not in logResult; | |
* 3. record should not in logRefund; | |
* if LogResult exists user should use the withdraw pattern userWithdrawPendingTransactions | |
* if LogRefund exists means manual refund has been done before | |
*/ | |
function ownerRefundUser(bytes32 originalUserBetId, address sendTo, uint originalUserProfit, uint originalUserBetValue) public | |
onlyOwner | |
{ | |
/* safely reduce pendingPayouts by userProfit[rngId] */ | |
maxPendingPayouts = safeSub(maxPendingPayouts, originalUserProfit); | |
/* send refund */ | |
sendTo.transfer(originalUserBetValue); | |
/* log refunds */ | |
emit LogRefund(originalUserBetId, sendTo, originalUserBetValue, now); | |
} | |
/* only owner address can set emergency pause #1 */ | |
function ownerPauseGame(bool newStatus) public | |
onlyOwner | |
{ | |
gamePaused = newStatus; | |
} | |
/* only owner address can set emergency pause #2 */ | |
function ownerPausePayouts(bool newPayoutStatus) public | |
onlyOwner | |
{ | |
payoutsPaused = newPayoutStatus; | |
} | |
/* only owner address can set treasury address */ | |
function ownerSetTreasury(address newTreasury) public | |
onlyOwner | |
{ | |
treasury = newTreasury; | |
} | |
/* only owner address can set owner address */ | |
function ownerChangeOwner(address newOwner) public | |
onlyOwner | |
{ | |
owner = newOwner; | |
} | |
/* only owner can set timeout */ | |
function ownerChangeTimeout(uint8 _timeout) public | |
onlyOwner | |
{ | |
timeout = _timeout; | |
} | |
/* only owner address can suicide - emergency */ | |
function ownerkill() public | |
onlyOwner | |
{ | |
selfdestruct(owner); | |
} | |
//@test function | |
function getClosePrice (string _indexName, uint _date) public { | |
emit logUint("price",closePrices[_indexName][_date]); | |
} | |
//@test function | |
function getContractBalance() public{ | |
emit logUint("contract balance", address(this).balance); | |
} | |
//@test function to set index close price | |
function setClosePrice(string _indexName, uint _indexDate, uint _closePrice) public { | |
closePrices[_indexName][_indexDate]= _closePrice; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment