/** * Source Code first verified at https://etherscan.io on Thursday, March 21, 2019 (UTC) */ pragma solidity ^0.4.24; /* * gibmireinbier - Full Stack Blockchain Developer * 0xA4a799086aE18D7db6C4b57f496B081b44888888 * [email protected] */ /* CHANGELOGS: . GrandPot : 2 rewards per round 5% pot with winner rate 100%, no dividends, no fund to F2M contract 15% pot with winner rate dynamic, init at 1st round with rate = ((initGrandPot * 2 * 100 / 68) * avgMul / SLP) + 1000; push 12% to F2M contract (10% dividends, 2% fund) => everybody happy! // --REMOVED if total tickets > rate then 100% got winner no winner : increased in the next round got winner : decreased in the next round . Bounty : received 30% bought tickets of bountyhunter back in the next round instead of ETH (bonus tickets not included) . SBountys : received 10% bought tickets of sbountyhunter back in the next round instead of ETH (bonus tickets not included) claimable only in pending time between 2 rounds . 1% bought tickets as bonus to next round (1st buy turn) (bonus Tickets not included) . Jackpot: rates increased from (1/Rate1, 1/Rate2) to (1/(Rate1 - 1), 1/(Rate2 - 1)) after each buy and reseted if jackpot triggered. jackpot rate = (ethAmount / 0.1) * rate, max winrate = 1/2 no dividends, no fund to F2M contract . Ticketsnumbers start from 1 in every rounds . Multiplier system: . all tickets got same weight = 1 . 2 decimals in stead of 0 . Multi = (grandPot / initGrandPot) * x * y * z x = (11 - timer1) / 4 + 1 (unit = hour(s), max = 15/4) timer1 updated real time, but x got delay 60 seconds from last buy y = (6 - timer2) / 3 + 1 (unit = day(s), max = 3) z = 4 if isGoldenMin, else 1 GOLDEN MINUTE : realTime, set = 8 atm that means from x: 08 : 00 to x:08:59 is goldenMin and z = 4 . Waiting time between 2 rounds 24 hours -> 18 hours . addPot : 80% -> grandPot 10% -> majorPot 10% -> minorPot BUGS FIXED: . Jackpot rate increased with multi = ethAmount / 0.1 (ether) no more split buy required . GrandPot getWeightRange() problems AUTHORS: . Seizo : Tickets bonus per 100 tickets, setLastRound anytime remove pushDividend when grandPot, jackpot triggered, called only on tickets buying remove round0 . Clark : Multiplier system 0xd9cd43AD9cD04183b5083E9E6c8DD0CE0c08eDe3 . GMEB : Tickets bounty system, Math. formulas 0xA4a799086aE18D7db6C4b57f496B081b44888888 . Kuroo Hazama : Dynamic rate 0x1E55fa952FCBc1f917746277C9C99cf65D53EbC8 */ library Helper { using SafeMath for uint256; uint256 constant public ZOOM = 1000; uint256 constant public SDIVIDER = 3450000; uint256 constant public PDIVIDER = 3450000; uint256 constant public RDIVIDER = 1580000; // Starting LS price (SLP) uint256 constant public SLP = 0.002 ether; // Starting Added Time (SAT) uint256 constant public SAT = 30; // seconds // Price normalization (PN) uint256 constant public PN = 777; // EarlyIncome base uint256 constant public PBASE = 13; uint256 constant public PMULTI = 26; uint256 constant public LBase = 1; uint256 constant public ONE_HOUR = 3600; uint256 constant public ONE_DAY = 24 * ONE_HOUR; //uint256 constant public TIMEOUT0 = 3 * ONE_HOUR; uint256 constant public TIMEOUT1 = 12 * ONE_HOUR; uint256 constant public TIMEOUT2 = 7 * ONE_DAY; function bytes32ToString (bytes32 data) public pure returns (string) { bytes memory bytesString = new bytes(32); for (uint j=0; j<32; j++) { byte char = byte(bytes32(uint(data) * 2 ** (8 * j))); if (char != 0) { bytesString[j] = char; } } return string(bytesString); } function uintToBytes32(uint256 n) public pure returns (bytes32) { return bytes32(n); } function bytes32ToUint(bytes32 n) public pure returns (uint256) { return uint256(n); } function stringToBytes32(string memory source) public pure returns (bytes32 result) { bytes memory tempEmptyStringTest = bytes(source); if (tempEmptyStringTest.length == 0) { return 0x0; } assembly { result := mload(add(source, 32)) } } function stringToUint(string memory source) public pure returns (uint256) { return bytes32ToUint(stringToBytes32(source)); } function uintToString(uint256 _uint) public pure returns (string) { return bytes32ToString(uintToBytes32(_uint)); } /* function getSlice(uint256 begin, uint256 end, string text) public pure returns (string) { bytes memory a = new bytes(end-begin+1); for(uint i = 0; i <= end - begin; i++){ a[i] = bytes(text)[i + begin - 1]; } return string(a); } */ function validUsername(string _username) public pure returns(bool) { uint256 len = bytes(_username).length; // Im Raum [4, 18] if ((len < 4) || (len > 18)) return false; // Letzte Char != ' ' if (bytes(_username)[len-1] == 32) return false; // Erste Char != '0' return uint256(bytes(_username)[0]) != 48; } // Lottery Helper // Seconds added per LT = SAT - ((Current no. of LT + 1) / SDIVIDER)^6 function getAddedTime(uint256 _rTicketSum, uint256 _tAmount) public pure returns (uint256) { //Luppe = 10000 = 10^4 uint256 base = (_rTicketSum + 1).mul(10000) / SDIVIDER; uint256 expo = base; expo = expo.mul(expo).mul(expo); // ^3 expo = expo.mul(expo); // ^6 // div 10000^6 expo = expo / (10**24); if (expo > SAT) return 0; return (SAT - expo).mul(_tAmount); } function getNewEndTime(uint256 toAddTime, uint256 slideEndTime, uint256 fixedEndTime) public view returns(uint256) { uint256 _slideEndTime = (slideEndTime).add(toAddTime); uint256 timeout = _slideEndTime.sub(block.timestamp); // timeout capped at TIMEOUT1 if (timeout > TIMEOUT1) timeout = TIMEOUT1; _slideEndTime = (block.timestamp).add(timeout); // Capped at fixedEndTime if (_slideEndTime > fixedEndTime) return fixedEndTime; return _slideEndTime; } // get random in range [1, _range] with _seed function getRandom(uint256 _seed, uint256 _range) public pure returns(uint256) { if (_range == 0) return _seed; return (_seed % _range) + 1; } function getEarlyIncomeMul(uint256 _ticketSum) public pure returns(uint256) { // Early-Multiplier = 1 + PBASE / (1 + PMULTI * ((Current No. of LT)/RDIVIDER)^6) uint256 base = _ticketSum * ZOOM / RDIVIDER; uint256 expo = base.mul(base).mul(base); //^3 expo = expo.mul(expo) / (ZOOM**6); //^6 return (1 + PBASE / (1 + expo.mul(PMULTI))); } // get reveiced Tickets, based on current round ticketSum function getTAmount(uint256 _ethAmount, uint256 _ticketSum) public pure returns(uint256) { uint256 _tPrice = getTPrice(_ticketSum); return _ethAmount.div(_tPrice); } function isGoldenMin( uint256 _slideEndTime ) public view returns(bool) { uint256 _restTime1 = _slideEndTime.sub(block.timestamp); // golden min. exist if timer1 < 6 hours if (_restTime1 > 6 hours) return false; uint256 _min = (block.timestamp / 60) % 60; return _min == 8; } // percent ZOOM = 100, ie. mul = 2.05 return 205 // Lotto-Multiplier = ((grandPot / initGrandPot)^2) * x * y * z // x = (TIMEOUT1 - timer1 - 1) / 4 + 1 => (unit = hour, max = 11/4 + 1 = 3.75) // y = (TIMEOUT2 - timer2 - 1) / 3 + 1) => (unit = day max = 3) // z = isGoldenMin ? 4 : 1 function getTMul( uint256 _initGrandPot, uint256 _grandPot, uint256 _slideEndTime, uint256 _fixedEndTime ) public view returns(uint256) { uint256 _pZoom = 100; uint256 base = _initGrandPot != 0 ?_pZoom.mul(_grandPot) / _initGrandPot : _pZoom; uint256 expo = base.mul(base); uint256 _timer1 = _slideEndTime.sub(block.timestamp) / 1 hours; // 0.. 11 uint256 _timer2 = _fixedEndTime.sub(block.timestamp) / 1 days; // 0 .. 6 uint256 x = (_pZoom * (11 - _timer1) / 4) + _pZoom; // [1, 3.75] uint256 y = (_pZoom * (6 - _timer2) / 3) + _pZoom; // [1, 3] uint256 z = isGoldenMin(_slideEndTime) ? 4 : 1; uint256 res = expo.mul(x).mul(y).mul(z) / (_pZoom ** 3); // ~ [1, 90] return res; } // get ticket price, based on current round ticketSum //unit in ETH, no need / zoom^6 function getTPrice(uint256 _ticketSum) public pure returns(uint256) { uint256 base = (_ticketSum + 1).mul(ZOOM) / PDIVIDER; uint256 expo = base; expo = expo.mul(expo).mul(expo); // ^3 expo = expo.mul(expo); // ^6 uint256 tPrice = SLP + expo / PN; return tPrice; } // used to draw grandpot results // weightRange = roundWeight * grandpot / (grandpot - initGrandPot) // grandPot = initGrandPot + round investedSum(for grandPot) function getWeightRange(uint256 initGrandPot) public pure returns(uint256) { uint256 avgMul = 30; return ((initGrandPot * 2 * 100 / 68) * avgMul / SLP) + 1000; } // dynamic rate _RATE = n // major rate = 1/n with _RATE = 1000 999 ... 1 // minor rate = 1/n with _RATE = 500 499 ... 1 // loop = _ethAmount / _MIN // lose rate = ((n- 1) / n) * ((n- 2) / (n - 1)) * ... * ((n- k) / (n - k + 1)) = (n - k) / n function isJackpot( uint256 _seed, uint256 _RATE, uint256 _MIN, uint256 _ethAmount ) public pure returns(bool) { // _RATE >= 2 uint256 k = _ethAmount / _MIN; if (k == 0) return false; // LOSE RATE MIN 50%, WIN RATE MAX 50% uint256 _loseCap = _RATE / 2; // IF _RATE - k > _loseCap if (_RATE > k + _loseCap) _loseCap = _RATE - k; bool _lose = (_seed % _RATE) < _loseCap; return !_lose; } } contract Lottery { using SafeMath for uint256; modifier withdrawRight(){ require(msg.sender == address(bankContract), "Bank only"); _; } modifier onlyDevTeam() { require(msg.sender == devTeam, "only for development team"); _; } modifier buyable() { require(block.timestamp > round[curRoundId].startTime, "not ready to sell Ticket"); require(block.timestamp < round[curRoundId].slideEndTime, "round over"); require(block.number <= round[curRoundId].keyBlockNr, "round over"); _; } modifier notStarted() { require(block.timestamp < round[curRoundId].startTime, "round started"); _; } enum RewardType { Minor, Major, Grand, Bounty } // 1 buy = 1 slot = _ethAmount => (tAmount, tMul) struct Slot { address buyer; uint256 rId; // ticket numbers in range and unique in all rounds uint256 tNumberFrom; uint256 tNumberTo; uint256 ethAmount; uint256 salt; } struct Round { // earlyIncome weight sum uint256 rEarlyIncomeWeight; // blockNumber to get hash as random seed uint256 keyBlockNr; mapping(address => bool) pBonusReceived; mapping(address => uint256) pBoughtTicketSum; mapping(address => uint256) pTicketSum; mapping(address => uint256) pInvestedSum; // early income weight by address mapping(address => uint256) pEarlyIncomeWeight; mapping(address => uint256) pEarlyIncomeCredit; mapping(address => uint256) pEarlyIncomeClaimed; // early income per weight uint256 ppw; // endTime increased every slot sold // endTime limited by fixedEndTime uint256 startTime; uint256 slideEndTime; uint256 fixedEndTime; // ticketSum from round 1 to this round uint256 ticketSum; // investedSum from round 1 to this round uint256 investedSum; // number of slots from round 1 to this round uint256 slotSum; } // round started with this grandPot amount, // used to calculate the rate for grandPot results // init in roundInit function uint256 public initGrandPot; Slot[] public slot; // slotId logs by address mapping( address => uint256[]) pSlot; mapping( address => uint256) public pSlotSum; // logs by address mapping( address => uint256) public pTicketSum; mapping( address => uint256) public pInvestedSum; CitizenInterface public citizenContract; F2mInterface public f2mContract; BankInterface public bankContract; RewardInterface public rewardContract; address public devTeam; uint256 constant public ZOOM = 1000; uint256 constant public ONE_HOUR = 60 * 60; uint256 constant public ONE_DAY = 24 * ONE_HOUR; uint256 constant public TIMEOUT0 = 3 * ONE_HOUR; uint256 constant public TIMEOUT1 = 12 * ONE_HOUR; uint256 constant public TIMEOUT2 = 7 * ONE_DAY; uint256 constant public FINALIZE_WAIT_DURATION = 60; // 60 Seconds uint256 constant public NEWROUND_WAIT_DURATION = 18 * ONE_HOUR; // 24 Hours // 15 seconds on Ethereum, 12 seconds used instead to make sure blockHash unavaiable // when slideEndTime reached // keyBlockNumber will be estimated again after every slot buy uint256 constant public BLOCK_TIME = 12; uint256 constant public MAX_BLOCK_DISTANCE = 250; uint256 constant public TBONUS_RATE = 100; uint256 public CASHOUT_REQ = 1; uint256 public GRAND_RATE; uint256 public MAJOR_RATE = 1001; uint256 public MINOR_RATE = 501; uint256 constant public MAJOR_MIN = 0.1 ether; uint256 constant public MINOR_MIN = 0.1 ether; //Bonus Tickets : Bounty + 7 sBounty uint256 public bountyPercent = 30; uint256 public sBountyPercent = 10; //uint256 public toNextPotPercent = 27; uint256 public grandRewardPercent = 15; uint256 public sGrandRewardPercent = 5; uint256 public jRewardPercent = 60; uint256 public toTokenPercent = 12; // 10% dividends 2% fund uint256 public toBuyTokenPercent = 1; uint256 public earlyIncomePercent = 22; uint256 public toRefPercent = 15; // sum == 100% = toPotPercent/100 * investedSum // uint256 public grandPercent = 80; //68; uint256 public majorPercent = 10; // 24; uint256 public minorPercent = 10; // 8; uint256 public grandPot; uint256 public majorPot; uint256 public minorPot; uint256 public curRoundId; uint256 public lastRoundId = 88888888; mapping (address => uint256) public rewardBalance; // used to save gas on earlyIncome calculating, curRoundId never included // only earlyIncome from round 1st to curRoundId-1 are fixed mapping (address => uint256) public lastWithdrawnRound; mapping (address => uint256) public earlyIncomeScannedSum; mapping (uint256 => Round) public round; // Current Round // first SlotId in last Block to fire jackpot uint256 public jSlot; // jackpot results of all slots in same block will be drawed at the same time, // by player, who buys the first slot in next block uint256 public lastBlockNr; // added by slot salt after every slot buy // does not matter with overflow uint256 public curRSalt; // ticket sum of current round uint256 public curRTicketSum; uint256 public lastBuyTime; uint256 public lastEndTime; uint256 constant multiDelayTime = 60; constructor (address _devTeam) public { // register address in network DevTeamInterface(_devTeam).setLotteryAddress(address(this)); devTeam = _devTeam; } // _contract = [f2mAddress, bankAddress, citizenAddress, lotteryAddress, rewardAddress, whitelistAddress]; function joinNetwork(address[6] _contract) public { require(address(citizenContract) == 0x0,"already setup"); f2mContract = F2mInterface(_contract[0]); bankContract = BankInterface(_contract[1]); citizenContract = CitizenInterface(_contract[2]); //lotteryContract = LotteryInterface(lotteryAddress); rewardContract = RewardInterface(_contract[4]); } function activeFirstRound() public onlyDevTeam() { require(curRoundId == 0, "already activated"); initRound(); GRAND_RATE = getWeightRange(); } // Core Functions function pushToPot() public payable { addPot(msg.value); } function checkpoint() private { // dummy slot between every 2 rounds // dummy slot never win jackpot cause of min 0.1 ETH Slot memory _slot; // _slot.tNumberTo = round[curRoundId].ticketSum; slot.push(_slot); Round memory _round; _round.startTime = NEWROUND_WAIT_DURATION.add(block.timestamp); // started with 3 hours timeout _round.slideEndTime = TIMEOUT0 + _round.startTime; _round.fixedEndTime = TIMEOUT2 + _round.startTime; _round.keyBlockNr = genEstKeyBlockNr(_round.slideEndTime); _round.ticketSum = round[curRoundId].ticketSum; _round.investedSum = round[curRoundId].investedSum; _round.slotSum = slot.length; curRoundId = curRoundId + 1; round[curRoundId] = _round; initGrandPot = grandPot; curRTicketSum = 0; } // from round 28+ function -- REMOVED function isLastRound() public view returns(bool) { return (curRoundId == lastRoundId); } function goNext() private { grandPot = 0; majorPot = 0; minorPot = 0; f2mContract.pushDividends.value(this.balance)(); // never start round[curRoundId].startTime = block.timestamp * 10; round[curRoundId].slideEndTime = block.timestamp * 10 + 1; CASHOUT_REQ = 0; } function initRound() private { // update all Round Log checkpoint(); if (isLastRound()) goNext(); updateMulti(); } function finalizeable() public view returns(bool) { uint256 finalizeTime = FINALIZE_WAIT_DURATION.add(round[curRoundId].slideEndTime); if (finalizeTime > block.timestamp) return false; // too soon to finalize if (getEstKeyBlockNr(curRoundId) >= block.number) return false; //block hash not exist return curRoundId > 0; } // bounty function finalize() public { require(finalizeable(), "Not ready to draw results"); uint256 _pRoundTicketSum = round[curRoundId].pBoughtTicketSum[msg.sender]; uint256 _bountyTicketSum = _pRoundTicketSum * bountyPercent / 100; endRound(msg.sender, _bountyTicketSum); initRound(); mintSlot(msg.sender, _bountyTicketSum, 0, 0); } function mintReward( address _lucker, uint256 _winNr, uint256 _slotId, uint256 _value, RewardType _rewardType) private { // add reward balance if its not Bounty Type and winner != 0x0 if ((_rewardType != RewardType.Bounty) && (_lucker != 0x0)) rewardBalance[_lucker] = rewardBalance[_lucker].add(_value); // reward log rewardContract.mintReward( _lucker, curRoundId, _winNr, slot[_slotId].tNumberFrom, slot[_slotId].tNumberTo, _value, uint256(_rewardType) ); } function mintSlot( address _buyer, // uint256 _rId, // ticket numbers in range and unique in all rounds // uint256 _tNumberFrom, // uint256 _tNumberTo, uint256 _tAmount, uint256 _ethAmount, uint256 _salt ) private { uint256 _tNumberFrom = curRTicketSum + 1; uint256 _tNumberTo = _tNumberFrom + _tAmount - 1; Slot memory _slot; _slot.buyer = _buyer; _slot.rId = curRoundId; _slot.tNumberFrom = _tNumberFrom; _slot.tNumberTo = _tNumberTo; _slot.ethAmount = _ethAmount; _slot.salt = _salt; slot.push(_slot); updateTicketSum(_buyer, _tAmount); round[curRoundId].slotSum = slot.length; pSlot[_buyer].push(slot.length - 1); } function jackpot() private { // get blocknumber to get blockhash uint256 keyBlockNr = getKeyBlockNr(lastBlockNr);//block.number; // salt not effected by jackpot, too risk uint256 seed = getSeed(keyBlockNr); // slot numberic from 1 ... totalSlot(round) // jackpot for all slot in last block, jSlot <= i <= lastSlotId (=slotSum - 1) // _to = first Slot in new block //uint256 _to = round[curRoundId].slotSum; uint256 jReward; // uint256 toF2mAmount; address winner; // jackpot check for slots in last block while (jSlot + 1 < round[curRoundId].slotSum) { // majorPot if (MAJOR_RATE > 2) MAJOR_RATE--; if (Helper.isJackpot(seed, MAJOR_RATE, MAJOR_MIN, slot[jSlot].ethAmount)){ winner = slot[jSlot].buyer; jReward = majorPot / 100 * jRewardPercent; mintReward(winner, 0, jSlot, jReward, RewardType.Major); majorPot = majorPot - jReward; MAJOR_RATE = 1001; } seed = seed + jSlot; // minorPot if (MINOR_RATE > 2) MINOR_RATE--; if (Helper.isJackpot(seed, MINOR_RATE, MINOR_MIN, slot[jSlot].ethAmount)){ winner = slot[jSlot].buyer; jReward = minorPot / 100 * jRewardPercent; mintReward(winner, 0, jSlot, jReward, RewardType.Minor); minorPot = minorPot - jReward; MINOR_RATE = 501; } seed = seed + jSlot; jSlot++; } } function endRound(address _bountyHunter, uint256 _bountyTicketSum) private { // GRAND_RATE = GRAND_RATE * 9 / 10; // REMOVED uint256 _rId = curRoundId; uint256 keyBlockNr = getKeyBlockNr(round[_rId].keyBlockNr); // curRSalt SAFE, CHECKED uint256 _seed = getSeed(keyBlockNr) + curRSalt; uint256 onePercent = grandPot / 100; // 0 : 5% grandPot, 100% winRate // 1 : 15% grandPot, dynamic winRate uint256[2] memory rGrandReward = [ onePercent * sGrandRewardPercent, onePercent * grandRewardPercent ]; uint256[2] memory weightRange = [ curRTicketSum, GRAND_RATE > curRTicketSum ? GRAND_RATE : curRTicketSum ]; // REMOVED // uint256[2] memory toF2mAmount = [0, onePercent * toTokenPercent]; // 1st turn for small grandPot (val = 5% rate = 100%) // 2nd turn for big grandPot (val = 15%, rate = max(GRAND_RATE, curRTicketSum), 12% to F2M contract if got winner) for (uint256 i = 0; i < 2; i++){ address _winner = 0x0; uint256 _winSlot = 0; uint256 _winNr = Helper.getRandom(_seed, weightRange[i]); // if winNr > curRTicketSum => no winner this turn // win Slot : fromWeight <= winNr <= toWeight // got winner this rolling turn if (_winNr <= curRTicketSum) { // grandPot -= rGrandReward[i] + toF2mAmount[i]; grandPot -= rGrandReward[i]; // big grandPot 15% if (i == 1) { GRAND_RATE = GRAND_RATE * 2; // f2mContract.pushDividends.value(toF2mAmount[i])(); } _winSlot = getWinSlot(_winNr); _winner = slot[_winSlot].buyer; _seed = _seed + (_seed / 10); } mintReward(_winner, _winNr, _winSlot, rGrandReward[i], RewardType.Grand); } mintReward(_bountyHunter, 0, 0, _bountyTicketSum, RewardType.Bounty); rewardContract.resetCounter(curRoundId); GRAND_RATE = (GRAND_RATE / 100) * 99 + 1; } function buy(string _sSalt) public payable { buyFor(_sSalt, msg.sender); } function updateInvested(address _buyer, uint256 _ethAmount) private { round[curRoundId].investedSum += _ethAmount; round[curRoundId].pInvestedSum[_buyer] += _ethAmount; pInvestedSum[_buyer] += _ethAmount; } function updateTicketSum(address _buyer, uint256 _tAmount) private { round[curRoundId].ticketSum = round[curRoundId].ticketSum + _tAmount; round[curRoundId].pTicketSum[_buyer] = round[curRoundId].pTicketSum[_buyer] + _tAmount; curRTicketSum = curRTicketSum + _tAmount; pTicketSum[_buyer] = pTicketSum[_buyer] + _tAmount; } function updateEarlyIncome(address _buyer, uint256 _pWeight) private { round[curRoundId].rEarlyIncomeWeight = _pWeight.add(round[curRoundId].rEarlyIncomeWeight); round[curRoundId].pEarlyIncomeWeight[_buyer] = _pWeight.add(round[curRoundId].pEarlyIncomeWeight[_buyer]); round[curRoundId].pEarlyIncomeCredit[_buyer] = round[curRoundId].pEarlyIncomeCredit[_buyer].add(_pWeight.mul(round[curRoundId].ppw)); } function getBonusTickets(address _buyer) private returns(uint256) { if (round[curRoundId].pBonusReceived[_buyer]) return 0; round[curRoundId].pBonusReceived[_buyer] = true; return round[curRoundId - 1].pBoughtTicketSum[_buyer] / TBONUS_RATE; } function updateMulti() private { if (lastBuyTime + multiDelayTime < block.timestamp) { lastEndTime = round[curRoundId].slideEndTime; } lastBuyTime = block.timestamp; } function buyFor(string _sSalt, address _sender) public payable buyable() { uint256 _salt = Helper.stringToUint(_sSalt); uint256 _ethAmount = msg.value; uint256 _ticketSum = curRTicketSum; require(_ethAmount >= Helper.getTPrice(_ticketSum), "not enough to buy 1 ticket"); // investedSum logs updateInvested(_sender, _ethAmount); updateMulti(); // update salt curRSalt = curRSalt + _salt; uint256 _tAmount = Helper.getTAmount(_ethAmount, _ticketSum); uint256 _tMul = getTMul(); // 100x Zoomed uint256 _pMul = Helper.getEarlyIncomeMul(_ticketSum); uint256 _pWeight = _pMul.mul(_tAmount); uint256 _toAddTime = Helper.getAddedTime(_ticketSum, _tAmount); addTime(curRoundId, _toAddTime); _tAmount = _tAmount.mul(_tMul) / 100; round[curRoundId].pBoughtTicketSum[_sender] += _tAmount; mintSlot(_sender, _tAmount + getBonusTickets(_sender), _ethAmount, _salt); // EarlyIncome Weight // ppw and credit zoomed x1000 // earlyIncome mul of each ticket in this slot updateEarlyIncome(_sender, _pWeight); // first slot in this block draw jacpot results for // all slot in last block if (lastBlockNr != block.number) { jackpot(); lastBlockNr = block.number; } distributeSlotBuy(_sender, curRoundId, _ethAmount); round[curRoundId].keyBlockNr = genEstKeyBlockNr(round[curRoundId].slideEndTime); } function distributeSlotBuy(address _sender, uint256 _rId, uint256 _ethAmount) private { uint256 onePercent = _ethAmount / 100; uint256 toF2mAmount = onePercent * toTokenPercent; // 12 uint256 toRefAmount = onePercent * toRefPercent; // 10 uint256 toBuyTokenAmount = onePercent * toBuyTokenPercent; //1 uint256 earlyIncomeAmount = onePercent * earlyIncomePercent; //27 uint256 taxAmount = toF2mAmount + toRefAmount + toBuyTokenAmount + earlyIncomeAmount; // 50 uint256 taxedEthAmount = _ethAmount.sub(taxAmount); // 50 addPot(taxedEthAmount); // 10% Ref citizenContract.pushRefIncome.value(toRefAmount)(_sender); // 2% Fund + 10% Dividends f2mContract.pushDividends.value(toF2mAmount)(); // 1% buy Token f2mContract.buyFor.value(toBuyTokenAmount)(_sender); // 27% Early uint256 deltaPpw = (earlyIncomeAmount * ZOOM).div(round[_rId].rEarlyIncomeWeight); round[_rId].ppw = deltaPpw.add(round[_rId].ppw); } function claimEarlyIncomebyAddress(address _buyer) private { if (curRoundId == 0) return; claimEarlyIncomebyAddressRound(_buyer, curRoundId); uint256 _rId = curRoundId - 1; while ((_rId > lastWithdrawnRound[_buyer]) && (_rId + 20 > curRoundId)) { earlyIncomeScannedSum[_buyer] += claimEarlyIncomebyAddressRound(_buyer, _rId); _rId = _rId - 1; } } function claimEarlyIncomebyAddressRound(address _buyer, uint256 _rId) private returns(uint256) { uint256 _amount = getCurEarlyIncomeByAddressRound(_buyer, _rId); if (_amount == 0) return 0; round[_rId].pEarlyIncomeClaimed[_buyer] = _amount.add(round[_rId].pEarlyIncomeClaimed[_buyer]); rewardBalance[_buyer] = _amount.add(rewardBalance[_buyer]); return _amount; } function withdrawFor(address _sender) public withdrawRight() returns(uint256) { if (curRoundId == 0) return; claimEarlyIncomebyAddress(_sender); lastWithdrawnRound[_sender] = curRoundId - 1; uint256 _amount = rewardBalance[_sender]; rewardBalance[_sender] = 0; bankContract.pushToBank.value(_amount)(_sender); return _amount; } function addTime(uint256 _rId, uint256 _toAddTime) private { round[_rId].slideEndTime = Helper.getNewEndTime(_toAddTime, round[_rId].slideEndTime, round[_rId].fixedEndTime); } // distribute to 3 pots Grand, Majorm Minor function addPot(uint256 _amount) private { uint256 onePercent = _amount / 100; uint256 toMinor = onePercent * minorPercent; uint256 toMajor = onePercent * majorPercent; uint256 toGrand = _amount - toMinor - toMajor; minorPot = minorPot + toMinor; majorPot = majorPot + toMajor; grandPot = grandPot + toGrand; } ////////////////////////////////////////////////////////////////// // READ FUNCTIONS ////////////////////////////////////////////////////////////////// function isWinSlot(uint256 _slotId, uint256 _keyNumber) public view returns(bool) { return (slot[_slotId - 1].tNumberTo < _keyNumber) && (slot[_slotId].tNumberTo >= _keyNumber); } function getWeightRange() public view returns(uint256) { return Helper.getWeightRange(initGrandPot); } function getWinSlot(uint256 _keyNumber) public view returns(uint256) { // return 0 if not found uint256 _to = slot.length - 1; uint256 _from = round[curRoundId-1].slotSum + 1; // dummy slot ignore uint256 _pivot; //Slot memory _slot; uint256 _pivotTo; // Binary search while (_from <= _to) { _pivot = (_from + _to) / 2; _pivotTo = slot[_pivot].tNumberTo; if (isWinSlot(_pivot, _keyNumber)) return _pivot; if (_pivotTo < _keyNumber) { // in right side _from = _pivot + 1; } else { // in left side _to = _pivot - 1; } } return _pivot; // never happens or smt gone wrong } // Key Block in future function genEstKeyBlockNr(uint256 _endTime) public view returns(uint256) { if (block.timestamp >= _endTime) return block.number + 8; uint256 timeDist = _endTime - block.timestamp; uint256 estBlockDist = timeDist / BLOCK_TIME; return block.number + estBlockDist + 8; } // get block hash of first block with blocktime > _endTime function getSeed(uint256 _keyBlockNr) public view returns (uint256) { // Key Block not mined atm if (block.number <= _keyBlockNr) return block.number; return uint256(blockhash(_keyBlockNr)); } // current reward balance function getRewardBalance(address _buyer) public view returns(uint256) { return rewardBalance[_buyer]; } // GET endTime function getSlideEndTime(uint256 _rId) public view returns(uint256) { return(round[_rId].slideEndTime); } function getFixedEndTime(uint256 _rId) public view returns(uint256) { return(round[_rId].fixedEndTime); } function getTotalPot() public view returns(uint256) { return grandPot + majorPot + minorPot; } // EarlyIncome function getEarlyIncomeByAddress(address _buyer) public view returns(uint256) { uint256 _sum = earlyIncomeScannedSum[_buyer]; uint256 _fromRound = lastWithdrawnRound[_buyer] + 1; // >=1 if (_fromRound + 100 < curRoundId) _fromRound = curRoundId - 100; uint256 _rId = _fromRound; while (_rId <= curRoundId) { _sum = _sum + getEarlyIncomeByAddressRound(_buyer, _rId); _rId++; } return _sum; } // included claimed amount function getEarlyIncomeByAddressRound(address _buyer, uint256 _rId) public view returns(uint256) { uint256 _pWeight = round[_rId].pEarlyIncomeWeight[_buyer]; uint256 _ppw = round[_rId].ppw; uint256 _rCredit = round[_rId].pEarlyIncomeCredit[_buyer]; uint256 _rEarlyIncome = ((_ppw.mul(_pWeight)).sub(_rCredit)).div(ZOOM); return _rEarlyIncome; } function getCurEarlyIncomeByAddress(address _buyer) public view returns(uint256) { uint256 _sum = 0; uint256 _fromRound = lastWithdrawnRound[_buyer] + 1; // >=1 if (_fromRound + 100 < curRoundId) _fromRound = curRoundId - 100; uint256 _rId = _fromRound; while (_rId <= curRoundId) { _sum = _sum.add(getCurEarlyIncomeByAddressRound(_buyer, _rId)); _rId++; } return _sum; } function getCurEarlyIncomeByAddressRound(address _buyer, uint256 _rId) public view returns(uint256) { uint256 _rEarlyIncome = getEarlyIncomeByAddressRound(_buyer, _rId); return _rEarlyIncome.sub(round[_rId].pEarlyIncomeClaimed[_buyer]); } //////////////////////////////////////////////////////////////////// function getEstKeyBlockNr(uint256 _rId) public view returns(uint256) { return round[_rId].keyBlockNr; } function getKeyBlockNr(uint256 _estKeyBlockNr) public view returns(uint256) { require(block.number > _estKeyBlockNr, "blockHash not avaiable"); uint256 jump = (block.number - _estKeyBlockNr) / MAX_BLOCK_DISTANCE * MAX_BLOCK_DISTANCE; return _estKeyBlockNr + jump; } // Logs function getCurRoundId() public view returns(uint256) { return curRoundId; } function getTPrice() public view returns(uint256) { return Helper.getTPrice(curRTicketSum); } function getTMul() public view returns(uint256) { return Helper.getTMul( initGrandPot, grandPot, lastBuyTime + multiDelayTime < block.timestamp ? round[curRoundId].slideEndTime : lastEndTime, round[curRoundId].fixedEndTime ); } function getPMul() public view returns(uint256) { return Helper.getEarlyIncomeMul(curRTicketSum); } function getPTicketSumByRound(uint256 _rId, address _buyer) public view returns(uint256) { return round[_rId].pTicketSum[_buyer]; } function getTicketSumToRound(uint256 _rId) public view returns(uint256) { return round[_rId].ticketSum; } function getPInvestedSumByRound(uint256 _rId, address _buyer) public view returns(uint256) { return round[_rId].pInvestedSum[_buyer]; } function getInvestedSumToRound(uint256 _rId) public view returns(uint256) { return round[_rId].investedSum; } function getPSlotLength(address _sender) public view returns(uint256) { return pSlot[_sender].length; } function getSlotLength() public view returns(uint256) { return slot.length; } function getSlotId(address _sender, uint256 i) public view returns(uint256) { return pSlot[_sender][i]; } function getSlotInfo(uint256 _slotId) public view returns(address, uint256[4], string) { Slot memory _slot = slot[_slotId]; return (_slot.buyer,[_slot.rId, _slot.tNumberFrom, _slot.tNumberTo, _slot.ethAmount], Helper.uintToString(_slot.salt)); } function cashoutable(address _address) public view returns(bool) { // need 1 ticket of curRound or lastRound in waiting time to start new round if (round[curRoundId].pTicketSum[_address] >= CASHOUT_REQ) return true; if (round[curRoundId].startTime > block.timestamp) { // underflow return false uint256 _lastRoundTickets = getPTicketSumByRound(curRoundId - 1, _address); if (_lastRoundTickets >= CASHOUT_REQ) return true; } return false; } // set endRound, prepare to upgrade new version function setLastRound(uint256 _lastRoundId) public onlyDevTeam() { require(_lastRoundId >= 8 && _lastRoundId > curRoundId, "too early to end"); require(lastRoundId == 88888888, "already set"); lastRoundId = _lastRoundId; } function sBountyClaim(address _sBountyHunter) public notStarted() returns(uint256) { require(msg.sender == address(rewardContract), "only Reward contract can manage sBountys"); uint256 _lastRoundTickets = round[curRoundId - 1].pBoughtTicketSum[_sBountyHunter]; uint256 _sBountyTickets = _lastRoundTickets * sBountyPercent / 100; mintSlot(_sBountyHunter, _sBountyTickets, 0, 0); return _sBountyTickets; } /* TEST */ // function forceEndRound() // public // { // round[curRoundId].keyBlockNr = block.number; // round[curRoundId].slideEndTime = block.timestamp; // round[curRoundId].fixedEndTime = block.timestamp; // } // function setTimer1(uint256 _hours) // public // { // round[curRoundId].slideEndTime = block.timestamp + _hours * 1 hours + 60; // round[curRoundId].keyBlockNr = genEstKeyBlockNr(round[curRoundId].slideEndTime); // } // function setTimer2(uint256 _days) // public // { // round[curRoundId].fixedEndTime = block.timestamp + _days * 1 days + 60; // require(round[curRoundId].fixedEndTime >= round[curRoundId].slideEndTime, "invalid test data"); // } } interface F2mInterface { function joinNetwork(address[6] _contract) public; // one time called // function disableRound0() public; function activeBuy() public; // function premine() public; // Dividends from all sources (DApps, Donate ...) function pushDividends() public payable; /** * Converts all of caller's dividends to tokens. */ function buyFor(address _buyer) public payable; function sell(uint256 _tokenAmount) public; function exit() public; function devTeamWithdraw() public returns(uint256); function withdrawFor(address sender) public returns(uint256); function transfer(address _to, uint256 _tokenAmount) public returns(bool); /*---------- ADMINISTRATOR ONLY FUNCTIONS ----------*/ function setAutoBuy() public; /*========================================== = PUBLIC FUNCTIONS = ==========================================*/ function ethBalance(address _address) public view returns(uint256); function myBalance() public view returns(uint256); function myEthBalance() public view returns(uint256); function swapToken() public; function setNewToken(address _newTokenAddress) public; } interface CitizenInterface { function joinNetwork(address[6] _contract) public; /*---------- ADMINISTRATOR ONLY FUNCTIONS ----------*/ function devTeamWithdraw() public; /*---------- WRITE FUNCTIONS ----------*/ function updateUsername(string _sNewUsername) public; //Sources: Token contract, DApps function pushRefIncome(address _sender) public payable; function withdrawFor(address _sender) public payable returns(uint256); function devTeamReinvest() public returns(uint256); /*---------- READ FUNCTIONS ----------*/ function getRefWallet(address _address) public view returns(uint256); } interface DevTeamInterface { function setF2mAddress(address _address) public; function setLotteryAddress(address _address) public; function setCitizenAddress(address _address) public; function setBankAddress(address _address) public; function setRewardAddress(address _address) public; function setWhitelistAddress(address _address) public; function setupNetwork() public; } interface BankInterface { function joinNetwork(address[6] _contract) public; function pushToBank(address _player) public payable; } interface RewardInterface { function mintReward( address _lucker, uint256 curRoundId, uint256 _winNr, uint256 _tNumberFrom, uint256 _tNumberTo, uint256 _value, uint256 _rewardType) public; function joinNetwork(address[6] _contract) public; function pushBounty(uint256 _curRoundId) public payable; function resetCounter(uint256 _curRoundId) public; } /** * @title SafeMath * @dev Math operations with safety checks that revert on error */ library SafeMath { int256 constant private INT256_MIN = -2**255; /** * @dev Multiplies two unsigned integers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b); return c; } /** * @dev Multiplies two signed integers, reverts on overflow. */ function mul(int256 a, int256 b) internal pure returns (int256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } require(!(a == -1 && b == INT256_MIN)); // This is the only case of overflow not detected by the check below int256 c = a * b; require(c / a == b); return c; } /** * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Integer division of two signed integers truncating the quotient, reverts on division by zero. */ function div(int256 a, int256 b) internal pure returns (int256) { require(b != 0); // Solidity only automatically asserts when dividing by 0 require(!(b == -1 && a == INT256_MIN)); // This is the only case of overflow int256 c = a / b; return c; } /** * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Subtracts two signed integers, reverts on overflow. */ function sub(int256 a, int256 b) internal pure returns (int256) { int256 c = a - b; require((b >= 0 && c <= a) || (b < 0 && c > a)); return c; } /** * @dev Adds two unsigned integers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } /** * @dev Adds two signed integers, reverts on overflow. */ function add(int256 a, int256 b) internal pure returns (int256) { int256 c = a + b; require((b >= 0 && c >= a) || (b < 0 && c < a)); return c; } /** * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), * reverts when dividing by zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0); return a % b; } }