//by nightman //2018.08.15 draft pragma solidity ^0.4.24; contract ZEROxRACER { //VARIABLES AND CONSTANTS //owner variables address public owner; uint256 public devFeeRate = 2; //2% uint256 public devBalance; uint256 public precisionFactor = 6; //shares precise to 00.0001% //team variables string public teamOnePrefix; // eg 'dead'; string public teamTwoPrefix; // eg 'beef'; address private teamOneRefAddress; // eg 0xdead000000000000000000000000000000000000; address private teamTwoRefAddress; // eg 0xbeeF000000000000000000000000000000000000; reference address formated with correct checksums because solidity is finicky uint256 public teamOneId = 1; uint256 public teamTwoId = 2; //user variables address[] public teamOneMembers; mapping (address => bool) public isTeamOneMember; mapping (address => uint256) public teamOneStake; address[] public teamTwoMembers; mapping (address => bool) public isTeamTwoMember; mapping (address => uint256) public teamTwoStake; //round variables uint256 public divRate = 20; //20% uint256 public pot = 0; uint256 public teamOneVolume = 0; uint256 public teamTwoVolume = 0; bool public currentRoundOpen = false; bool public lastRoundResolved = true; uint256 public timerStart = 60 minutes; //default uint256 public timerMax = 6 hours; //default uint256 public roundStartTime; uint256 public roundEndTime; //round-idenpendant user balance tracking mapping (address => uint256) public userBalance; //CONSTRUCTOR constructor() public { owner = msg.sender; } //MODIFIERS modifier onlyOwner() { require (msg.sender == owner); _; } modifier validAddress() { require (checkAddress(msg.sender, 1) == true || checkAddress(msg.sender, 2) == true); _; } modifier gameOpen() { require (currentRoundOpen == true); require (lastRoundResolved == false); require (now < roundEndTime); _; } //EVENTS event potFunded(address _funder, uint256 _amount); event teamBuy(address _buyer, uint256 _amount, uint256 _teamID); event roundEnded(uint256 _winningTeamId, string _winningTeamString, uint256 _pot); event newRoundStarted(uint256 _timeStart, uint256 _timeMax, string _team1, string _team2, uint256 _seed); //DEV FUNCTIONS //start round function openRound (uint _timerStart, uint _timerMax, string _team1, string _team2, address _team1add, address _team2add) public payable onlyOwner() { require (currentRoundOpen == false); require (lastRoundResolved == true); require (msg.value > 0); //must give a seed currentRoundOpen = true; lastRoundResolved = false; timerStart = _timerStart; timerMax = _timerMax; roundStartTime = now; roundEndTime = now + timerStart; pot += msg.value; teamOneRefAddress = _team1add; teamTwoRefAddress = _team2add; teamOnePrefix = _team1; teamTwoPrefix = _team2; emit newRoundStarted(timerStart, timerMax, _team1, _team2, msg.value); } //dev withdraw function devWithdraw() public onlyOwner() { require (devBalance > 0); assert(devBalance <= address(this).balance); owner.transfer(devBalance); devBalance = 0; } //force advance; @dev not sure if this is necessary or not function devForceAdvance() public onlyOwner { require (currentRoundOpen == true && now > roundEndTime + 48 hours); //this can only be called in the event that something went majorly wrong with the resolve round function, for example if it ran out of gas updatating balances owner.transfer(pot); //for manual payback currentRoundOpen = false; lastRoundResolved = true; } //PUBLIC FUNCTIONS function fundPot() public payable { require (currentRoundOpen == true && lastRoundResolved == false); pot += msg.value; emit potFunded(msg.sender, msg.value); } function buy(uint256 _teamID) public payable validAddress() gameOpen() { //@dev: remove validAddress() modifier for testing require (msg.value >= 1 finney); require (roundEndTime > now); if (_teamID == 1 && teamOneMembers.length == 0 || _teamID == 2 && teamTwoMembers.length == 0) { pot += msg.value; } else { uint256 divContribution = uint256(SafeMaths.div(SafeMaths.mul(msg.value, divRate), 100)); //divFees uint256 potContribution = msg.value - divContribution; pot += potContribution; distributeDivs(divContribution, _teamID); divContribution = 0; } timeAdjustPlus(); if (_teamID == 1) { if (isTeamOneMember[msg.sender] == false) { isTeamOneMember[msg.sender] = true; teamOneMembers.push(msg.sender); } teamOneStake[msg.sender] += msg.value; teamOneVolume += msg.value; } else if (_teamID == 2) { if (isTeamTwoMember[msg.sender] == false) { isTeamTwoMember[msg.sender] = true; teamTwoMembers.push(msg.sender); } teamTwoStake[msg.sender] += msg.value; teamTwoVolume += msg.value; } emit teamBuy(msg.sender, msg.value, _teamID); } function resolveRound() public { require (now > roundEndTime); //require (currentRoundOpen == true); if (teamOneVolume > teamTwoVolume) { teamOneWin(); } else if (teamOneVolume < teamTwoVolume) { teamTwoWin(); } else if (teamOneVolume == teamTwoVolume) { tie(); } currentRoundOpen = false; lastRoundResolved = true; } function userWithdraw() public { require (userBalance[msg.sender] > 0); assert(userBalance[msg.sender] <= address(this).balance); msg.sender.transfer(userBalance[msg.sender]); userBalance[msg.sender] = 0; } function reduceTime() public payable { timeAdjustNeg(); pot += msg.value; } //VIEW FUNCTIONS function checkAddress(address _input, uint256 _teamID) public view returns(bool) { if (_teamID == 1) { checkAddressTeamOne(_input); } else if (_teamID == 2) { checkAddressTeamTwo(_input); } else { return false; } } //addresschecks function checkAddressTeamOne(address _input) public view returns(bool) { bytes2 a = addressToBytePostion(teamOneRefAddress); bytes2 b = addressToBytePostion(_input); if (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))) { return true; //returns true if prefix matches first 2 bytes of teamOnePrefix } } function checkAddressTeamTwo(address _input) public view returns(bool) { bytes2 a = addressToBytePostion(teamTwoRefAddress); bytes2 b = addressToBytePostion(_input); if (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))) { return true; //returns true if prefix matches first 2 bytes of teamTwoPrefix } } //generally useful lookup functions function currentRoundInfo() public view gameOpen() returns(uint256 _pot, uint256 _teamOneVolume, uint256 _teamTwoVolume, uint256 _timerStart, uint256 _timerMax, uint256 _roundStartTime, uint256 _roundEndTime) { return (pot, teamOneVolume, teamTwoVolume, timerStart, timerMax, roundStartTime, roundEndTime); } function getTimeLeft() public view returns(uint256 _timeLeftSeconds) { if (roundEndTime - now < 0) { return 0; } else { return roundEndTime - now; } } function teamOneTotalPlayers() public view returns(uint256 _teamOnePlayerCount) { return teamOneMembers.length; } function teamTwoTotalPlayers() public view returns(uint256 _teamTwoPlayerCount) { return teamTwoMembers.length; } function totalPlayers() public view returns(uint256 _totalPlayerCount) { return teamOneMembers.length + teamTwoMembers.length; } function adjustedPotBalance() public view returns(uint256 _adjustedPotBalance) { uint256 potAdjusted = uint256(SafeMaths.div(SafeMaths.mul(pot, devFeeRate), 100)); return potAdjusted; } function contractBalance() public view returns(uint256 _contractBalance) { return address(this).balance; } //INTERNAL FUNCTIONS function timeAdjustPlus() internal { if (msg.value >= 1 finney) { uint256 timeFactor = 1000000000000000; //one finney in wei uint256 timeShares = uint256(SafeMaths.div(msg.value, timeFactor)); if (timeShares + roundEndTime > now + timerMax) { roundEndTime = now + timerMax; } else { roundEndTime += timeShares; }//add one second per finney } } function timeAdjustNeg() internal { if (msg.value >= 1 finney) { uint256 timeFactor = 1000000000000000; //one finney in wei uint256 timeShares = uint256(SafeMaths.div(msg.value, timeFactor)); if (roundEndTime - timeShares < now) { roundEndTime = now; currentRoundOpen = false; } else { roundEndTime -= timeShares; } } } //divs //@dev fix the math on this function distributeDivs(uint256 _divContribution, uint256 _teamID) internal { if (_teamID == 1) { for (uint256 i = 0; i < teamOneMembers.length; i++) { address user = teamOneMembers[i]; uint256 shareRate = SafeMaths.div(SafeMaths.div(SafeMaths.mul(teamOneStake[user], 10 ** (precisionFactor + 1)), teamOneVolume) + 5, 10); uint256 share = uint256(SafeMaths.div(SafeMaths.mul(_divContribution, shareRate), 10 ** precisionFactor)); userBalance[user] += share; share = 0; } } else if (_teamID == 2) { for (i = 0; i < teamTwoMembers.length; i++) { user = teamTwoMembers[i]; shareRate = SafeMaths.div(SafeMaths.div(SafeMaths.mul(teamTwoStake[user], 10 ** (precisionFactor + 1)), teamTwoVolume) + 5, 10); share = uint256(SafeMaths.div(SafeMaths.mul(_divContribution, shareRate), 10 ** precisionFactor)); userBalance[user] += share; share = 0; } } } //round payouts function teamOneWin() internal { uint256 devShare = uint256(SafeMaths.div(SafeMaths.mul(pot, devFeeRate), 100)); //devFeeRate devBalance += devShare; uint256 potAdjusted = pot - devShare; uint256 totalTeamStake = 0; emit roundEnded(1, teamOnePrefix, potAdjusted); for (uint256 i = 0; i < teamOneMembers.length; i++) { address user = teamOneMembers[i]; uint256 shareRate = SafeMaths.div(SafeMaths.div(SafeMaths.mul(teamOneStake[user], 10 ** (precisionFactor + 1)), teamOneVolume) + 5, 10); uint256 share = uint256(SafeMaths.div(SafeMaths.mul(potAdjusted, shareRate), 10 ** precisionFactor));//@dev make sure this isn't leaking percentage points on rounding userBalance[user] += share; totalTeamStake += share; teamOneStake[user] = 0; isTeamOneMember[user] = false; } for (uint256 j = 0; j < teamTwoMembers.length; j++) { user = teamTwoMembers[j]; teamTwoStake[user] = 0; //is this necessary if I also clear the array? probably not isTeamTwoMember[user] = false; } if (totalTeamStake < potAdjusted) { devBalance += SafeMaths.sub(potAdjusted, totalTeamStake); //dev gets extra wei dust from rounding } teamOneMembers.length = 0; teamTwoMembers.length = 0; pot = 0; teamOneVolume = 0; teamTwoVolume = 0; } function teamTwoWin() internal { uint256 devShare = uint256(SafeMaths.div(SafeMaths.mul(pot, devFeeRate), 100)); //devFeeRate devBalance += devShare; uint256 potAdjusted = pot - devShare; uint256 totalTeamStake = 0; emit roundEnded(2, teamTwoPrefix, potAdjusted); for (uint256 i = 0; i < teamTwoMembers.length; i++) { address user = teamTwoMembers[i]; uint256 shareRate = SafeMaths.div(SafeMaths.div(SafeMaths.mul(teamTwoStake[user], 10 ** (precisionFactor + 1)), teamTwoVolume) + 5, 10); uint256 share = uint256(SafeMaths.div(SafeMaths.mul(potAdjusted, shareRate), 10 ** precisionFactor));//@dev make sure this isn't leaking percentage points on rounding userBalance[user] += share; totalTeamStake += share; teamTwoStake[user] = 0; isTeamTwoMember[user] = false; } for (uint256 j = 0; j < teamOneMembers.length; j++) { user = teamOneMembers[j]; teamOneStake[user] = 0; //is this necessary if I also clear the array? probably not isTeamOneMember[user] = false; } if (totalTeamStake < potAdjusted) { devBalance += SafeMaths.sub(potAdjusted, totalTeamStake); //dev gets extra wei dust from rounding } teamOneMembers.length = 0; teamTwoMembers.length = 0; pot = 0; teamOneVolume = 0; teamTwoVolume = 0; } function tie() internal { uint256 devShare = uint256(SafeMaths.div(SafeMaths.mul(pot, devFeeRate), 100)); //devFeeRate devBalance += devShare; uint256 potAdjusted = pot - devShare; uint256 totalTeamStake = 0; emit roundEnded(0, 'a tie? wtf', potAdjusted); for (uint256 i = 0; i < teamOneMembers.length; i++) { address user = teamOneMembers[i]; uint256 shareRate = SafeMaths.div(SafeMaths.div(SafeMaths.mul(teamOneStake[user], 10 ** (precisionFactor + 1)), teamOneVolume) + 5, 10); uint256 share = uint256(SafeMaths.div(SafeMaths.mul(potAdjusted, shareRate), 10 ** precisionFactor));//@dev make sure this isn't leaking percentage points on rounding userBalance[user] += share; totalTeamStake += share; teamOneStake[user] = 0; isTeamOneMember[user] = false; } for (uint256 j = 0; j < teamTwoMembers.length; j++) { user = teamTwoMembers[j]; shareRate = SafeMaths.div(SafeMaths.div(SafeMaths.mul(teamTwoStake[user], 10 ** (precisionFactor + 1)), teamTwoVolume) + 5, 10); share = uint256(SafeMaths.div(SafeMaths.mul(potAdjusted, shareRate), 10 ** precisionFactor));//@dev make sure this isn't leaking percentage points on rounding userBalance[user] += share; totalTeamStake += share; teamTwoStake[user] = 0; isTeamTwoMember[user] = false; } if (totalTeamStake < potAdjusted) { devBalance += SafeMaths.sub(potAdjusted, totalTeamStake); //dev gets extra wei dust from rounding so contract can zero out } teamOneMembers.length = 0; teamTwoMembers.length = 0; pot = 0; teamOneVolume = 0; teamTwoVolume = 0; } //internal functions for prefix check //@dev refactor to support various byte lengths function toBytes(address a) internal pure returns (bytes b) { assembly { let m := mload(0x40) mstore(add(m, 20), xor(0x140000000000000000000000000000000000000000, a)) mstore(0x40, add(m, 52)) b := m } return b; } function bytes2AtPosition(bytes _input) internal pure returns (bytes2 output) { output = _input[0]; return output; } function addressToBytePostion(address _input) internal pure returns (bytes2 output) { output = bytes2AtPosition(toBytes(_input)); return output; } } //LIBRARIES library SafeMaths { function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; assert(c / a == b); return c; } function div(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a / b; return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } //openround arguments for testing //600, 2000, "one", "two", "0x5c3c1540dfcd795b0aca58a496e3c30fe2405b07", "0x5c3c1540dfcd795b0aca58a496e3c30fe2405b07"
0.4.24