etherindex contract
/
contract Etherindex is DSSafeAddSub {
/
- 检查投注的合法性:
-
- 投注金额 > 0 todo:目前只检查了这一个条件
-
- 目标收益 > 0
-
- 最大收益 > 目标收益
-
- 投注日期合法
-
- 预测区间上限 > 下限
-
- 预测区间下限 > 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 == oraclizeLib.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; // 最大待付金额
uint public totalBets = 244612; // 初始化合约中断数据 - 总投注次数
uint public totalWeiWon = 110633844560463069959901; // 初始化合约中断数据 - 用户赢取的总数
uint public totalWeiWagered = 316486087709317593009320; // 初始化合约中断数据 - 总投注金额
/*
- oraclize vars
*/
// query type constant
uint8 constant VALIDATECHECK = 0;
uint8 constant PAYOUTCHECK = 1;
mapping (bytes32 => uint8) public queryType; // 将queryId和query type绑定
mapping(string => mapping(uint => closePrice)) closePrices; // 实际收盘价 string代表指数名,一个uint代表收盘日期
struct closePrice {
uint price;
bool valid;
}
/*
- 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, uint _userRewardValue, uint _userProfit, uint _userBetValue, uint _userIntervalLow, uint _userIntervalhigh, uint8 _betStatus);
/* 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, uint _userRewardValue, uint _userProfit, uint _userBetValue, uint _userIntervalLow, uint _userIntervalhigh, uint8 _betStatus);
/* log manual refunds /
event LogRefund(bytes32 indexed _userBetId, address indexed _userAddress, uint indexed RefundValue);
/ log owner transfers /
event LogOwnerTransfer(address indexed SentToAddress, uint indexed AmountTransferred);
/ owner fund contract */
event LogOwnerFundContract(address _ownerAddress, uint _currentContractBalance, uint _fundAmount, DateTime._DateTime _fundTimestamp);
//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;
//oraclizeLib.oraclize_setNetwork(networkID_auto);
/* use TLSNotary for oraclize call /
//oraclizeLib.oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS);
/ init gas for oraclize /
gasForOraclize = 235000;
/ init gas price for callback (default 20 gwei)*/
oraclizeLib.oraclize_setCustomGasPrice(20000000000 wei);
}
/*
-
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, uint _userIntervalLow, uint _userIntervalHigh, uint _userProfit, uint _userMaxProfitLimit, uint _userBetTimestamp) public
payable
systemIsActive
{
// 输入检查:
// 投注金额 > 0
require(msg.value > 0);
// 用户目标收益 < 本次投注允许的最大投注金额
require(_userProfit < _userMaxProfitLimit);
// 用户目标收益 > 0
require(_userProfit > 0);
// 投注日期合法 todo: 目前仅检查投注日期是否大于0
require(_userBetDate > 0);
// 用户投注区间下限 > 0
require(_userIntervalLow > 0);
// 用户投注区间上限 > 用户投注区间下限
require(_userIntervalHigh > _userIntervalLow);
// todo: api有待确认
bytes32 queryId = oraclizeLib.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] = _userIntervalLow;
userIntervalHigh[queryId] = _userIntervalHigh;
userBetTimestamp[queryId] = _userBetTimestamp;
// bet状态为初始状态
userBetStatus[queryId] = INITIAL;
// 记录投注信息
emit LogBet(queryId, msg.sender, safeAdd(msg.value, _userProfit), _userProfit, msg.value, _userIntervalLow, _userIntervalHigh, userBetStatus[queryId]);
}
//收盘时发起当日收盘价查询,每个指数只查询一次
function inqueryIndexPrice(string _indexName, uint _date) public onlyOwner returns(bool)
{
// 检查本地是否存有查询过的数据,无则调用oraclize查询,且返回true todo:api有待确认
if (closePrices[_indexName][_date].price == 0){
bytes32 queryId = oraclizeLib.oraclize_query("URL", "https://api.iextrading.com/1.0/stock/goog/price");
queryType[queryId] = PAYOUTCHECK;
//@test
emit logBool("inquery index price",true);
emit logBytes32("query id",queryId);
return true;
}
else{
//@test
emit logBool("inquery index price",false);
return false;
}
}
function payoutProcess(bytes32 myid) public{
//@test
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]].price);
emit logBool("close price valid",closePrices[userUnderlyingIndex[myid]][userBetDate[myid]].valid);
/* 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;
// refund
if (closePrices[userUnderlyingIndex[myid]][userBetDate[myid]].price == 0 || closePrices[userUnderlyingIndex[myid]][userBetDate[myid]].valid == false){
userBetStatus[myid] = REFUND;
emit LogResult(userBetId[myid], userAddress[myid], safeAdd(userBetValue[myid], userProfit[myid]), userProfit[myid], userBetValue[myid], userIntervalLow[myid], userIntervalHigh[myid], userBetStatus[myid]);
if(!userTempAddress[myid].send(userTempBetValue[myid])){
userBetStatus[myid] = REFUND_FAILED_SEND;
emit LogResult(userBetId[myid], userAddress[myid], safeAdd(userBetValue[myid], userProfit[myid]), userProfit[myid], userBetValue[myid], userIntervalLow[myid], userIntervalHigh[myid], userBetStatus[myid]);
/* if send failed let user withdraw via userWithdrawPendingTransactions */
userPendingWithdrawals[userTempAddress[myid]] = safeAdd(userPendingWithdrawals[userTempAddress[myid]], userTempBetValue[myid]);
}
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]].price && userIntervalHigh[myid] > closePrices[userUnderlyingIndex[myid]][userBetDate[myid]].price){
/* update total wei won */
totalWeiWon = safeAdd(totalWeiWon, userTempProfit[myid]);
/* safely calculate payout via profit plus original wager */
userTempProfit[myid] = safeAdd(userTempProfit[myid], userTempBetValue[myid]);
// set status to WIN
userBetStatus[myid] = WIN;
emit LogResult(userBetId[myid], userAddress[myid], safeAdd(userBetValue[myid], userProfit[myid]), userProfit[myid], userBetValue[myid], userIntervalLow[myid], userIntervalHigh[myid], userBetStatus[myid]);
//* update maximum profit todo: temp comment
// setMaxProfit();
/*
* 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(userTempProfit[myid])){
// set status to WIN_FAILED_SEND
userBetStatus[myid] = WIN_FAILED_SEND;
emit LogResult(userBetId[myid], userAddress[myid], safeAdd(userBetValue[myid], userProfit[myid]), userProfit[myid], userBetValue[myid], userIntervalLow[myid], userIntervalHigh[myid], userBetStatus[myid]);
/* if send failed let user withdraw via userWithdrawPendingTransactions */
userPendingWithdrawals[userTempAddress[myid]] = safeAdd(userPendingWithdrawals[userTempAddress[myid]], userTempProfit[myid]);
}
return;
}
/*
* no win
* send 1 wei to a losing bet
* update contract balance to calculate new max bet
*/
if(userIntervalLow[myid] > closePrices[userUnderlyingIndex[myid]][userBetDate[myid]].price || userIntervalHigh[myid] <= closePrices[userUnderlyingIndex[myid]][userBetDate[myid]].price){
// set status to WIN_FAILED_SEND
userBetStatus[myid] = LOSE;
emit LogResult(userBetId[myid], userAddress[myid], safeAdd(userBetValue[myid], userProfit[myid]), userProfit[myid], userBetValue[myid], userIntervalLow[myid], userIntervalHigh[myid], userBetStatus[myid]);
/*
* send 1 wei - external call to an untrusted contract
*/
if(!userTempAddress[myid].send(1)){
/* if send failed let user withdraw via userWithdrawPendingTransactions */
userPendingWithdrawals[userTempAddress[myid]] = safeAdd(userPendingWithdrawals[userTempAddress[myid]], 1);
}
return;
}
}
function __callback(bytes32 myid, string result, bytes proof) public
onlyOraclize
payoutsAreActive
{
//@test
emit trace("callback triggered");
// __callback的调用必须源自VALIDATECHECK, PAYOUTCHECK
require (queryType[myid] != VALIDATECHECK || queryType[myid] != PAYOUTCHECK);
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];
}
if (queryType[myid] == PAYOUTCHECK){
// 检查oraclize是否返回结果 todo: 需要加上检查价格,不为零
require (bytes(result).length != 0 && bytes(proof).length != 0);
// 将收盘价保存 todo: temp comment, 要求接口返回标的指数名和收盘日期
//closePrices[$indexname][$date].price = result;
//closePrices[$indexname][$date].valid = true;
// 记录收盘价查询日志,保证要查询到价格 todo: temp comment
//emit logIndexPrice(myid, $indexname, $date, $price, proof);
}
}
/*
- 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, DateTime.parseTimestamp(now));
}
/* set gas price for oraclize callback */
function ownerSetCallbackGasPrice(uint newCallbackGasPrice) public
onlyOwner
{
oraclizeLib.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);
}
/* only owner address can do manual refund
- used only if bet placed + oraclize failed to __callback
- filter LogBet by address and/or userBetId:
- LogBet(userBetId[rngId], userAddress[rngId], safeAdd(userBetValue[rngId], userProfit[rngId]), userProfit[rngId], userBetValue[rngId], userNumber[rngId]);
- check the following logs do not exist for userBetId and/or userAddress[rngId] before refunding:
- LogResult or LogRefund
- if LogResult exists user should use the withdraw pattern userWithdrawPendingTransactions
/
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);
}
/* 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 address can suicide - emergency */
function ownerkill() public
onlyOwner
{
selfdestruct(owner);
}
//@test function
function getUserInfo(bytes32 queryId) public {
emit logAddress("userAddress",userAddress[queryId]);
emit logAddress("userTempAddress",userTempAddress[queryId]);
emit logUint("userBetValue",userBetValue[queryId]);
emit logUint("userTempBetValue",userTempBetValue[queryId]);
emit logUint("userProfit",userProfit[queryId]);
emit logUint("userTempProfit",userTempProfit[queryId]);
emit logUint("userMaxProfitLimit",userMaxProfitLimit[queryId]);
emit logString("userUnderlyingIndex",userUnderlyingIndex[queryId]);
emit logUint("userBetDate",userBetDate[queryId]);
emit logUint("userIntervalLow",userIntervalLow[queryId]);
emit logUint("userIntervalHigh",userIntervalHigh[queryId]);
emit logUint("userBetTimestamp",userBetTimestamp[queryId]);
emit logUint8("loguserBetStatus",userBetStatus[queryId]);
emit logUint("userPendingWithdrawals",userPendingWithdrawals[userAddress[queryId]]);
emit logUint("userTempPendingWithdrawals",userPendingWithdrawals[userTempAddress[queryId]]);
}
//@test function
function getClosePrice (string _indexName, uint _date) public {
emit logUint("price",closePrices[_indexName][_date].price);
}