Skip to content

Instantly share code, notes, and snippets.

@huahuayu
Last active April 10, 2018 14:33
Show Gist options
  • Save huahuayu/06aa6557594226a9c90b675fef6a3c94 to your computer and use it in GitHub Desktop.
Save huahuayu/06aa6557594226a9c90b675fef6a3c94 to your computer and use it in GitHub Desktop.
etherindex
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