//by nightman //2018.08.09 pseudocode draft //untested pragma solidity ^0.4.24; contract ZEROxRACER { //VARIABLES AND CONSTANTS //owner variables address public owner; uint256 public devFeeRate = 98; //2% uint256 public devBalance; //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) isTeamOneMember; mapping (address => uint256) teamOneStake; address[] public teamTwoMembers; mapping (address => bool) isTeamTwoMember; mapping (address => uint256) teamTwoStake; //round variables 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) 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); 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() { pot += msg.value; timeAdjust(); 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); msg.sender.transfer(userBalance[msg.sender]); userBalance[msg.sender] = 0; } //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 teamTwoPrefix } } 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 getUserShareByTeam(address _user, uint256 _teamID) public view returns(uint256 _userStakePercentage, uint256 _userPayoutIfWin) { if (_teamID == 1) { uint256 potAdjusted = uint256(SafeMaths.div(SafeMaths.mul(pot, devFeeRate), 100)); uint256 shareRate = SafeMaths.div(teamOneVolume, teamOneStake[_user]); uint256 share = uint256(SafeMaths.div(SafeMaths.mul(potAdjusted, shareRate), 100)); return (shareRate, share); } else if (_teamID == 2) { potAdjusted = uint256(SafeMaths.div(SafeMaths.mul(pot, devFeeRate), 100)); shareRate = SafeMaths.div(teamTwoVolume, teamTwoStake[_user]); share = uint256(SafeMaths.div(SafeMaths.mul(potAdjusted, shareRate), 100)); return (shareRate, share); } } 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 timeAdjust() internal { if (msg.value > 1 finney) { uint256 timeFactor = 1000000000000000; //1 finney in wei uint256 timeShares = uint256(SafeMaths.div(SafeMaths.mul(msg.value, timeFactor), 100)); roundEndTime += timeShares; //add second per finney } } //round payouts function teamOneWin() internal { uint256 potAdjusted = uint256(SafeMaths.div(SafeMaths.mul(pot, devFeeRate), 100)); //devFeeRate devBalance += pot - potAdjusted; uint256 totalTeamStake = 0; emit roundEnded(1, teamOnePrefix, potAdjusted); for (uint256 i = 0; i < teamOneMembers.length; i++) { address user = teamOneMembers[i]; uint256 shareRate = SafeMaths.div(teamOneVolume, teamOneStake[user]); uint256 share = uint256(SafeMaths.div(SafeMaths.mul(potAdjusted, shareRate), 100));//@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 += 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 potAdjusted = uint256(SafeMaths.div(SafeMaths.mul(pot, devFeeRate), 100)); //devFeeRate devBalance += pot - potAdjusted; uint256 totalTeamStake = 0; emit roundEnded(2, teamTwoPrefix, potAdjusted); for (uint256 i = 0; i < teamTwoMembers.length; i++) { address user = teamTwoMembers[i]; uint256 shareRate = SafeMaths.div(teamTwoVolume, teamTwoStake[user]); uint256 share = uint256(SafeMaths.div(SafeMaths.mul(potAdjusted, shareRate), 100));//@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[i]; teamOneStake[user] = 0; //is this necessary if I also clear the array? probably not isTeamOneMember[user] = false; } if (totalTeamStake < potAdjusted) { devBalance += 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 potAdjusted = uint256(SafeMaths.div(SafeMaths.mul(pot, devFeeRate), 100)); //devFeeRate devBalance += pot - potAdjusted; 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(potAdjusted, teamOneStake[user]); uint256 share = uint256(SafeMaths.div(SafeMaths.mul(potAdjusted, shareRate), 100));//@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[i]; shareRate = SafeMaths.div(potAdjusted, teamTwoStake[user]); share = uint256(SafeMaths.div(SafeMaths.mul(potAdjusted, shareRate), 100));//@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 += 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; } }
0.4.18