pragma solidity^0.4.24;
/**
MOBIUS 2D
https://m2d.win
This game was inspired by FOMO3D. Our code is much cleaner and more efficient (built from scratch).
Some useless "features" like the teams were not implemented.
The Mobius2D game consists of rounds with guaranteed winners!
You buy "shares" (instad of keys) for a given round, and you get returns from investors after you.
The sare price is constant until the hard deadline, after which it increases exponentially.
If a round is inactive for a day it can end earlier than the hard deadline.
If a round runs longer, it is guaranteed to finish not much after the hard deadline (and the last investor gets the big jackpot).
Additionally, if you invest more than 0.1 ETH you get a chance to win an airdrop and you get bonus shares
Part of all funds also go to a big final jackpot - the last investor (before a round runs out) wins.
Payouts work in REAL TIME - you can withdraw your returns at any time!
Additionally, the first round is an ICO, so you'll also get our tokens by participating!
!!!!!!!!!!!!!!
Token holders will receive part of current and future revenue of this and any other game we develop!
!!!!!!!!!!!!!!
.................................. LAUGHING MAN sssyyhddmN..........................................
..........................Nmdyyso+/:--.``` :` `-`:--:/+ossyhdmN....................................
......................Ndhyso/:.` --. o. /+`o::` `` `-:+osyh..................................
..................MNdyso/-` /-`/:+./:/..` +. //.o +.+::+ -` `-/sshdN.............................
................Ndyso:` ` --:+`o//.-:-``` ... ``` - /::::/ +..-` ./osh............................
..............Nhso/. .-.:/`o--:`` `..-:::oss+::--.`` .:/::/`+-`/../sydN........................
............mhso-``-:+./:-:. .-/+osssssssssssssssssso+:-` -//o::+:/` .:oyhN......................
..........Nhso:` .+-./ ` .:+sssssso+//:-------:://+ossssso/---.`-`/:-o/ `:syd.....................
........Mdyo- +/../`-` ./osssso/-.` ``.:+ossss+:` `-+` ` `/sy....................
......MNys/` -:-/: -+ssss+-` `.:+ssss/. ` -+-. .osh..................
......mys- :-/+-` :osss+-` .:osss+. `//o:- `/syN................
....Mdso. --:-/- -osss+. `-osss+` :--://`-sy................
....dso-. ++:+ `/sss+. `:osss: `:.-+ -sy...............
..Mdso``+///.` .osss: `/sss+` :/-.. -syN.............
..mss` `+::/ .ssso. :sss+` `+:/+ -syN............
..ys- ``` .ssso` -sss+` `:::+:`/sh............
Mds+ `:/.. `osso` -sss+ -:`.` `ssN...........
Mys. `/+:: +sss/........................................................+sss:.....-::+sy..NN.......
ds+ :-/- .ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssyyhdN...
hs: `/+:: :/+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ossssyhNM
ss. `:::` ```` ``` ``-+sssyN
ss` /:-+` `o++: .:+oooo+/. `-/ooooo+-` -sssy
ss `:/: `sss/ :ooo++/++os/` .oso++/++oso. osss
ss``/:-- `sss/ ./.` `.:: /-.` ``-/` -sssy
ss.:::-:. ssso ` ` ``.-+sssyN
hs:`:/:/. /sss. .++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++oossssyhNM
ds+ `` .sss/ -ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssyyyyhmN...
Nss.:::::. +sss. +sss/........................................osss:...+sss:......../shmNNN.......
Mds+..-:::` `osso` `+sss: `+sss: -sss+ .:-.` `ssN...........
..ys- .+.:: .ssso` `/sss+. -osss: -sss+` `:++-` /sh............
..mss` .-. .ssso. :osss/` .+ssso. :sss+` `.:+:` -syN............
..Mdso` `--:` .osss: `/ssss/.` `-+ssso:` `/sss+` `++.-. -syN.............
....dso` -//+-` `/sss+. ./ossso/-`` `.:+sssso:` `:osss: .::+/. -sy...............
....Mdso. `-//-` -osss+. `-+ssssso+/:-.`````..-:/+osssso/. `-osss+.` -///- -sy................
......mys- `/://.` :osss+-` `-/+osssssssssssssssssso+:. .:osss+. .:`..-``/syN................
......MNys/` ..+-/: -+ssss+-` `.-://++oooo++/:-.` `.:+ssss/. .` .osh..................
........Mdyo- `::/. ` ./osssso/-.` ``.:+ossss+:` ` .//` `/sy....................
..........Nhso- :+:.` .:+sssssso+//:--------://+ossssso/:. `::/: --/.:syd.....................
............mhso-` ./+--+-: .-/+osssssssssssssssssso+/-. .+` `//-/ `::oyhN......................
..............Nhso/` +/:--+.-` `..-:::////::--.`` .`:/-o` ./`./sydN........................
................Ndys+:` ``--+++- .: `.`` `` -.`/:/`.o./::. ./osh............................
..................MNdyso/-` ` :` +- :+.o`s ::-/++`s`+/o.-:` `-/sshdN.............................
......................Ndhyso/:.` .+ +/:/ +:/-./:-`+: `` `.:+osyh..................................
..........................Nmdyyso+/:--/.`` ``..-:/+ossyhdmN....................................
..............................MN..dhhyyssssssssssssyyhddmN..........................................
*/
contract DSMath {
function add(uint x, uint y) internal pure returns (uint z) {
require((z = x + y) >= x);
}
function sub(uint x, uint y) internal pure returns (uint z) {
require((z = x - y) <= x);
}
function mul(uint x, uint y) internal pure returns (uint z) {
require(y == 0 || (z = x * y) / y == x);
}
function min(uint x, uint y) internal pure returns (uint z) {
return x <= y ? x : y;
}
function max(uint x, uint y) internal pure returns (uint z) {
return x >= y ? x : y;
}
function imin(int x, int y) internal pure returns (int z) {
return x <= y ? x : y;
}
function imax(int x, int y) internal pure returns (int z) {
return x >= y ? x : y;
}
uint constant WAD = 10 ** 18;
uint constant RAY = 10 ** 27;
function wmul(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, y), WAD / 2) / WAD;
}
function rmul(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, y), RAY / 2) / RAY;
}
function wdiv(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, WAD), y / 2) / y;
}
function rdiv(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, RAY), y / 2) / y;
}
// This famous algorithm is called "exponentiation by squaring"
// and calculates x^n with x as fixed-point and n as regular unsigned.
//
// It's O(log n), instead of O(n) for naive repeated multiplication.
//
// These facts are why it works:
//
// If n is even, then x^n = (x^2)^(n/2).
// If n is odd, then x^n = x * x^(n-1),
// and applying the equation for even x gives
// x^n = x * (x^2)^((n-1) / 2).
//
// Also, EVM division is flooring and
// floor[(n-1) / 2] = floor[n / 2].
//
function rpow(uint x, uint n) internal pure returns (uint z) {
z = n % 2 != 0 ? x : RAY;
for (n /= 2; n != 0; n /= 2) {
x = rmul(x, x);
if (n % 2 != 0) {
z = rmul(z, x);
}
}
}
}
contract DSAuthority {
function canCall(
address src, address dst, bytes4 sig
) public view returns (bool);
}
contract DSAuthEvents {
event LogSetAuthority (address indexed authority);
event LogSetOwner (address indexed owner);
}
contract DSAuth is DSAuthEvents {
DSAuthority public authority;
address public owner;
constructor() public {
owner = msg.sender;
emit LogSetOwner(msg.sender);
}
function setOwner(address owner_)
public
auth
{
owner = owner_;
emit LogSetOwner(owner);
}
function setAuthority(DSAuthority authority_)
public
auth
{
authority = authority_;
emit LogSetAuthority(authority);
}
modifier auth {
require(isAuthorized(msg.sender, msg.sig));
_;
}
function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
if (src == address(this)) {
return true;
} else if (src == owner) {
return true;
} else if (authority == DSAuthority(0)) {
return false;
} else {
return authority.canCall(src, this, sig);
}
}
}
interface MobiusToken {
function mint(address _to, uint _amount) external;
function finishMinting() external returns (bool);
function disburseDividends() external payable;
}
contract Mobius2D is DSMath, DSAuth {
// IPFS hash of the website - can be accessed even if our domain goes down.
// Just go to any public IPFS gateway and use this hash - e.g. ipfs.infura.io/ipfs/<ipfsHash>
string public ipfsHash;
string public ipfsHashType = "ipfs"; // can either be ipfs, or ipns
MobiusToken public token;
// In case of an upgrade, these variables will be set. An upgrade does not affect a currently running round,
// nor does it do anything with investors' vaults.
bool public upgraded;
address public nextVersion;
// Total stats
uint public totalSharesSold;
uint public totalEarningsGenerated;
uint public totalDividendsPaid;
uint public totalJackpotsWon;
// Fractions for where revenue goes
uint public constant DEV_FRACTION = WAD / 20; // 5% goes to devs
uint public constant DEV_DIVISOR = 20; // 5%
uint public constant RETURNS_FRACTION = 65 * 10**16; // 65% goes to share holders
// 1% if it is a referral purchase, this value will be taken from the above fraction (e.g. if 1% is for refferals, then 64% goes to returns)
uint public constant REFERRAL_FRACTION = 1 * 10**16;
uint public constant JACKPOT_SEED_FRACTION = WAD / 20; // 5% goes to the next round's jackpot
uint public constant JACKPOT_FRACTION = 15 * 10**16; // 15% goes to the final jackpot
uint public constant AIRDROP_FRACTION = WAD / 100; // 1% goes to airdrops
uint public constant DIVIDENDS_FRACTION = 9 * 10**16; // 9% goes to token holders!
uint public constant STARTING_SHARE_PRICE = 1 finney; // a 1000th of an ETH
uint public constant PRICE_INCREASE_PERIOD = 1 hours; // how often the price doubles after the hard deadline
uint public constant HARD_DEADLINE_DURATION = 30 days; // hard deadline is this much after the round start
uint public constant SOFT_DEADLINE_DURATION = 1 days; // max soft deadline
uint public constant TIME_PER_SHARE = 5 minutes; // how much time is added to the soft deadline per share purchased
uint public jackpotSeed;// Jackpot from previous rounds
uint public devBalance; // outstanding balance for devs
uint public raisedICO;
// Helpers to calculate returns - no funds are ever held on lockdown
uint public unclaimedReturns;
uint public constant MULTIPLIER = RAY;
// This represents an investor. No need to player IDs - they are useless (everyone already has a unique address).
// Just use native mappings (duh!)
struct Investor {
uint lastCumulativeReturnsPoints;
uint shares;
}
// This represents a round
struct MobiusRound {
uint totalInvested;
uint jackpot;
uint airdropPot;
uint totalShares;
uint cumulativeReturnsPoints; // this is to help calculate returns when the total number of shares changes
uint hardDeadline;
uint softDeadline;
uint price;
uint lastPriceIncreaseTime;
address lastInvestor;
bool finalized;
mapping (address => Investor) investors;
}
struct Vault {
uint totalReturns; // Total balance = returns + referral returns + jackpots/airdrops
uint refReturns; // how much of the total is from referrals
}
mapping (address => Vault) vaults;
uint public latestRoundID;// the first round has an ID of 0
MobiusRound[] rounds;
event SharesIssued(address indexed to, uint shares);
event ReturnsWithdrawn(address indexed by, uint amount);
event JackpotWon(address by, uint amount);
event AirdropWon(address by, uint amount);
event RoundStarted(uint indexed ID, uint hardDeadline);
event IPFSHashSet(string _type, string _hash);
constructor(address _token) public {
token = MobiusToken(_token);
}
// The return values will include all vault balance, but you must specify a roundID because
// Returns are not actually calculated in storage until you invest in the round or withdraw them
function estimateReturns(address investor, uint roundID) public view
returns (uint totalReturns, uint refReturns)
{
MobiusRound storage rnd = rounds[roundID];
uint outstanding;
if(rounds.length > 1) {
if(hasReturns(investor, roundID - 1)) {
MobiusRound storage prevRnd = rounds[roundID - 1];
outstanding = _outstandingReturns(investor, prevRnd);
}
}
outstanding += _outstandingReturns(investor, rnd);
totalReturns = vaults[investor].totalReturns + outstanding;
refReturns = vaults[investor].refReturns;
}
function hasReturns(address investor, uint roundID) public view returns (bool) {
MobiusRound storage rnd = rounds[roundID];
return rnd.cumulativeReturnsPoints > rnd.investors[investor].lastCumulativeReturnsPoints;
}
function investorInfo(address investor, uint roundID) external view
returns(uint shares, uint totalReturns, uint referralReturns)
{
MobiusRound storage rnd = rounds[roundID];
shares = rnd.investors[investor].shares;
(totalReturns, referralReturns) = estimateReturns(investor, roundID);
}
function roundInfo(uint roundID) external view
returns(
address leader,
uint price,
uint jackpot,
uint airdrop,
uint shares,
uint totalInvested,
uint distributedReturns,
uint _hardDeadline,
uint _softDeadline,
bool finalized
)
{
MobiusRound storage rnd = rounds[roundID];
leader = rnd.lastInvestor;
price = rnd.price;
jackpot = rnd.jackpot;
airdrop = rnd.airdropPot;
shares = rnd.totalShares;
totalInvested = rnd.totalInvested;
distributedReturns = wmul(rnd.totalInvested, RETURNS_FRACTION);
_hardDeadline = rnd.hardDeadline;
_softDeadline = rnd.softDeadline;
finalized = rnd.finalized;
}
function totalsInfo() external view
returns(
uint totalReturns,
uint totalShares,
uint totalDividends,
uint totalJackpots
) {
MobiusRound storage rnd = rounds[latestRoundID];
if(rnd.softDeadline > now) {
totalShares = totalSharesSold + rnd.totalShares;
totalReturns = totalEarningsGenerated + wmul(rnd.totalInvested, RETURNS_FRACTION);
totalDividends = totalDividendsPaid + wmul(rnd.totalInvested, DIVIDENDS_FRACTION);
} else {
totalShares = totalSharesSold;
totalReturns = totalEarningsGenerated;
totalDividends = totalDividendsPaid;
}
totalJackpots = totalJackpotsWon;
}
function () public payable {
buyShares(address(0x0));
}
/// Function to buy shares in the latest round. Purchase logic is abstracted
function buyShares(address ref) public payable {
if(rounds.length > 0) {
MobiusRound storage rnd = rounds[latestRoundID];
_purchase(rnd, msg.value, ref);
} else {
revert("Not yet started");
}
}
/// Function to purchase with what you have in your vault as returns
function reinvestReturns(uint value) public {
reinvestReturns(value, address(0x0));
}
function reinvestReturns(uint value, address ref) public {
MobiusRound storage rnd = rounds[latestRoundID];
_updateReturns(msg.sender, rnd);
require(vaults[msg.sender].totalReturns >= value, "Can't spend what you don't have");
vaults[msg.sender].totalReturns = sub(vaults[msg.sender].totalReturns, value);
vaults[msg.sender].refReturns = min(vaults[msg.sender].refReturns, vaults[msg.sender].totalReturns);
unclaimedReturns = sub(unclaimedReturns, value);
_purchase(rnd, value, ref);
}
function withdrawReturns() public {
MobiusRound storage rnd = rounds[latestRoundID];
if(rounds.length > 1) {// check if they also have returns from before
if(hasReturns(msg.sender, latestRoundID - 1)) {
MobiusRound storage prevRnd = rounds[latestRoundID - 1];
_updateReturns(msg.sender, prevRnd);
}
}
_updateReturns(msg.sender, rnd);
uint amount = vaults[msg.sender].totalReturns;
require(amount > 0, "Nothing to withdraw!");
unclaimedReturns = sub(unclaimedReturns, amount);
vaults[msg.sender].totalReturns = 0;
vaults[msg.sender].refReturns = 0;
rnd.investors[msg.sender].lastCumulativeReturnsPoints = rnd.cumulativeReturnsPoints;
msg.sender.transfer(amount);
emit ReturnsWithdrawn(msg.sender, amount);
}
// Manually update your returns for a given round in case you were inactive since before it ended
function updateMyReturns(uint roundID) public {
MobiusRound storage rnd = rounds[roundID];
_updateReturns(msg.sender, rnd);
}
function finalizeAndRestart() public payable {
finalizeLastRound();
startNewRound();
}
/// Anyone can start a new round
function startNewRound() public payable {
require(!upgraded, "This contract has been upgraded!");
if(rounds.length > 0) {
require(rounds[latestRoundID].finalized, "Previous round not finalized");
require(rounds[latestRoundID].softDeadline < now, "Previous round still running");
}
uint _rID = rounds.length++;
MobiusRound storage rnd = rounds[_rID];
latestRoundID = _rID;
rnd.lastInvestor = msg.sender;
rnd.price = STARTING_SHARE_PRICE;
rnd.hardDeadline = now + HARD_DEADLINE_DURATION;
rnd.softDeadline = now + SOFT_DEADLINE_DURATION;
rnd.jackpot = jackpotSeed;
jackpotSeed = 0;
_purchase(rnd, msg.value, address(0x0));
emit RoundStarted(_rID, rnd.hardDeadline);
}
/// Anyone can finalize a finished round
function finalizeLastRound() public {
MobiusRound storage rnd = rounds[latestRoundID];
_finalizeRound(rnd);
}
/// This is how devs pay the bills
function withdrawDevShare() public auth {
uint value = devBalance;
devBalance = 0;
msg.sender.transfer(value);
}
function setIPFSHash(string _type, string _hash) public auth {
ipfsHashType = _type;
ipfsHash = _hash;
emit IPFSHashSet(_type, _hash);
}
function upgrade(address _nextVersion) public auth {
require(_nextVersion != address(0x0), "Invalid Address!");
require(!upgraded, "Already upgraded!");
upgraded = true;
nextVersion = _nextVersion;
if(rounds[latestRoundID].finalized) {
//if last round was finalized (and no new round was started), transfer the jackpot seed to the new version
vaults[nextVersion].totalReturns = jackpotSeed;
jackpotSeed = 0;
}
}
/// Purchase logic
function _purchase(MobiusRound storage rnd, uint value, address ref) internal {
require(rnd.softDeadline >= now, "After deadline!");
require(value >= rnd.price/10, "Not enough Ether!");
rnd.totalInvested = add(rnd.totalInvested, value);
// Set the last investor (to win the jackpot after the deadline)
if(value >= rnd.price)
rnd.lastInvestor = msg.sender;
// Check out airdrop
_airDrop(rnd, value);
// Process revenue in different "buckets"
_splitRevenue(rnd, value, ref);
// Update returns before issuing shares
_updateReturns(msg.sender, rnd);
//issue shares for the current round. 1 share = 1 time increase for the deadline
uint newShares = _issueShares(rnd, msg.sender, value);
//Mint tokens during the first round
if(rounds.length == 1) {
token.mint(msg.sender, newShares);
}
uint timeIncreases = newShares/WAD;// since 1 share is represented by 1 * 10^18, divide by 10^18
// adjust soft deadline to new soft deadline
uint newDeadline = add(rnd.softDeadline, mul(timeIncreases, TIME_PER_SHARE));
rnd.softDeadline = min(newDeadline, now + SOFT_DEADLINE_DURATION);
// If after hard deadline, double the price every price increase periods
if(now > rnd.hardDeadline) {
if(now > rnd.lastPriceIncreaseTime + PRICE_INCREASE_PERIOD) {
rnd.price = rnd.price * 2;
rnd.lastPriceIncreaseTime = now;
}
}
}
function _finalizeRound(MobiusRound storage rnd) internal {
require(!rnd.finalized, "Already finalized!");
require(rnd.softDeadline < now, "Round still running!");
if(rounds.length == 1) {
// After finishing minting tokens they will be transferable and dividends will be available!
require(token.finishMinting(), "Couldn't finish minting tokens!");
}
// Transfer jackpot to winner's vault
vaults[rnd.lastInvestor].totalReturns = add(vaults[rnd.lastInvestor].totalReturns, rnd.jackpot);
unclaimedReturns = add(unclaimedReturns, rnd.jackpot);
emit JackpotWon(rnd.lastInvestor, rnd.jackpot);
totalJackpotsWon += rnd.jackpot;
// transfer the leftover to the next round's jackpot
jackpotSeed = add(jackpotSeed, wmul(rnd.totalInvested, JACKPOT_SEED_FRACTION));
//Empty the AD pot if it has a balance.
jackpotSeed = add(jackpotSeed, rnd.airdropPot);
if(upgraded) {
// if upgraded transfer the jackpot seed to the new version
vaults[nextVersion].totalReturns = jackpotSeed;
jackpotSeed = 0;
}
//Send out dividends to token holders
uint _div;
if(rounds.length == 1){
// 2% during the first round, and the normal fraction otherwise
_div = wmul(rnd.totalInvested, 2 * 10**16);
} else {
_div = wmul(rnd.totalInvested, DIVIDENDS_FRACTION);
}
token.disburseDividends.value(_div)();
totalDividendsPaid += _div;
totalSharesSold += rnd.totalShares;
totalEarningsGenerated += wmul(rnd.totalInvested, RETURNS_FRACTION);
rnd.finalized = true;
}
/**
This is where the magic happens: every investor gets an exact share of all returns proportional to their shares
If you're early, you'll have a larger share for longer, so obviously you earn more.
*/
function _updateReturns(address _investor, MobiusRound storage rnd) internal {
if(rnd.investors[_investor].shares == 0) {
return;
}
uint outstanding = _outstandingReturns(_investor, rnd);
// if there are any returns, transfer them to the investor's vaults
if (outstanding > 0) {
vaults[_investor].totalReturns = add(vaults[_investor].totalReturns, outstanding);
}
rnd.investors[_investor].lastCumulativeReturnsPoints = rnd.cumulativeReturnsPoints;
}
function _outstandingReturns(address _investor, MobiusRound storage rnd) internal view returns(uint) {
if(rnd.investors[_investor].shares == 0) {
return 0;
}
// check if there've been new returns
uint newReturns = sub(
rnd.cumulativeReturnsPoints,
rnd.investors[_investor].lastCumulativeReturnsPoints
);
uint outstanding = 0;
if(newReturns != 0) {
// outstanding returns = (total new returns points * ivestor shares) / MULTIPLIER
// The MULTIPLIER is used also at the point of returns disbursment
outstanding = mul(newReturns, rnd.investors[_investor].shares) / MULTIPLIER;
}
return outstanding;
}
/// Process revenue according to fractions
function _splitRevenue(MobiusRound storage rnd, uint value, address ref) internal {
uint roundReturns;
uint returnsOffset;
if(rounds.length == 1){
returnsOffset = 13 * 10**16;// during the first round reduce returns (by 13%) and give more to the ICO
}
if(ref != address(0x0)) {
// if there was a referral
roundReturns = wmul(value, RETURNS_FRACTION - REFERRAL_FRACTION - returnsOffset);
uint _ref = wmul(value, REFERRAL_FRACTION);
vaults[ref].totalReturns = add(vaults[ref].totalReturns, _ref);
vaults[ref].refReturns = add(vaults[ref].refReturns, _ref);
unclaimedReturns = add(unclaimedReturns, _ref);
} else {
roundReturns = wmul(value, RETURNS_FRACTION - returnsOffset);
}
uint airdrop = wmul(value, AIRDROP_FRACTION);
uint jackpot = wmul(value, JACKPOT_FRACTION);
uint dev;
// During the ICO, devs get 25% (5% originally, 7% from the dividends fraction,
// and 13% from the returns), leaving 2% for dividends, and 52% for returns
// This is only during the first round, and later rounds leave the original fractions:
// 5% for devs, 9% dividends, 65% returns
if(rounds.length == 1){
// calculate dividends at the end, no need to do it at every purchase
dev = value / 4; // 25%
raisedICO += dev;
} else {
dev = value / DEV_DIVISOR;
}
// if this is the first purchase, send to jackpot (no one can claim these returns otherwise)
if(rnd.totalShares == 0) {
rnd.jackpot = add(rnd.jackpot, roundReturns);
} else {
_disburseReturns(rnd, roundReturns);
}
rnd.airdropPot = add(rnd.airdropPot, airdrop);
rnd.jackpot = add(rnd.jackpot, jackpot);
devBalance = add(devBalance, dev);
}
function _disburseReturns(MobiusRound storage rnd, uint value) internal {
unclaimedReturns = add(unclaimedReturns, value);// keep track of unclaimed returns
// The returns points represent returns*MULTIPLIER/totalShares (at the point of purchase)
// This allows us to keep outstanding balances of shareholders when the total supply changes in real time
if(rnd.totalShares == 0) {
rnd.cumulativeReturnsPoints = mul(value, MULTIPLIER) / wdiv(value, rnd.price);
} else {
rnd.cumulativeReturnsPoints = add(
rnd.cumulativeReturnsPoints,
mul(value, MULTIPLIER) / rnd.totalShares
);
}
}
function _issueShares(MobiusRound storage rnd, address _investor, uint value) internal returns(uint) {
if(rnd.investors[_investor].lastCumulativeReturnsPoints == 0) {
rnd.investors[_investor].lastCumulativeReturnsPoints = rnd.cumulativeReturnsPoints;
}
uint newShares = wdiv(value, rnd.price);
//bonuses:
if(value >= 100 ether) {
newShares = mul(newShares, 2);//get double shares if you paid more than 100 ether
} else if(value >= 10 ether) {
newShares = add(newShares, newShares/2);//50% bonus
} else if(value >= 1 ether) {
newShares = add(newShares, newShares/3);//33% bonus
} else if(value >= 100 finney) {
newShares = add(newShares, newShares/10);//10% bonus
}
rnd.investors[_investor].shares = add(rnd.investors[_investor].shares, newShares);
rnd.totalShares = add(rnd.totalShares, newShares);
emit SharesIssued(_investor, newShares);
return newShares;
}
function _airDrop(MobiusRound storage rnd, uint value) internal {
require(msg.sender == tx.origin, "ONLY HOOMANS (or scripts that don't use smart contracts)!");
if(value > 100 finney) {
/**
Creates a random number from the last block hash and current timestamp.
One could add more seemingly random data like the msg.sender, etc, but that doesn't
make it harder for a miner to manipulate the result in their favor (if they intended to).
*/
uint chance = uint(keccak256(abi.encodePacked(blockhash(block.number - 1), now)));
if(chance % 200 == 0) {// once in 200 times
uint prize = rnd.airdropPot / 2;// win half of the pot, regardless of how much you paid
rnd.airdropPot = rnd.airdropPot / 2;
vaults[msg.sender].totalReturns = add(vaults[msg.sender].totalReturns, prize);
unclaimedReturns = add(unclaimedReturns, prize);
totalJackpotsWon += prize;
emit AirdropWon(msg.sender, prize);
}
}
}
}
{
"compilationTarget": {
"Mobius2D.sol": "Mobius2D"
},
"evmVersion": "byzantium",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":true,"inputs":[],"name":"raisedICO","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MULTIPLIER","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nextVersion","type":"address"}],"name":"upgrade","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"nextVersion","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"unclaimedReturns","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"DEV_DIVISOR","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"TIME_PER_SHARE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ipfsHashType","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalEarningsGenerated","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"investor","type":"address"},{"name":"roundID","type":"uint256"}],"name":"investorInfo","outputs":[{"name":"shares","type":"uint256"},{"name":"totalReturns","type":"uint256"},{"name":"referralReturns","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"roundID","type":"uint256"}],"name":"updateMyReturns","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"roundID","type":"uint256"}],"name":"roundInfo","outputs":[{"name":"leader","type":"address"},{"name":"price","type":"uint256"},{"name":"jackpot","type":"uint256"},{"name":"airdrop","type":"uint256"},{"name":"shares","type":"uint256"},{"name":"totalInvested","type":"uint256"},{"name":"distributedReturns","type":"uint256"},{"name":"_hardDeadline","type":"uint256"},{"name":"_softDeadline","type":"uint256"},{"name":"finalized","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"withdrawDevShare","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"investor","type":"address"},{"name":"roundID","type":"uint256"}],"name":"hasReturns","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"withdrawReturns","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"ref","type":"address"}],"name":"buyShares","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"finalizeAndRestart","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_type","type":"string"},{"name":"_hash","type":"string"}],"name":"setIPFSHash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalsInfo","outputs":[{"name":"totalReturns","type":"uint256"},{"name":"totalShares","type":"uint256"},{"name":"totalDividends","type":"uint256"},{"name":"totalJackpots","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STARTING_SHARE_PRICE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"jackpotSeed","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DIVIDENDS_FRACTION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SOFT_DEADLINE_DURATION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSharesSold","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"REFERRAL_FRACTION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"RETURNS_FRACTION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"HARD_DEADLINE_DURATION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"}],"name":"reinvestReturns","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"investor","type":"address"},{"name":"roundID","type":"uint256"}],"name":"estimateReturns","outputs":[{"name":"totalReturns","type":"uint256"},{"name":"refReturns","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"JACKPOT_SEED_FRACTION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"JACKPOT_FRACTION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"AIRDROP_FRACTION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"startNewRound","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"finalizeLastRound","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"upgraded","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"},{"name":"ref","type":"address"}],"name":"reinvestReturns","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"ipfsHash","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalDividendsPaid","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"devBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalJackpotsWon","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEV_FRACTION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"latestRoundID","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PRICE_INCREASE_PERIOD","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"token","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_token","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"shares","type":"uint256"}],"name":"SharesIssued","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"by","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ReturnsWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"by","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"JackpotWon","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"by","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"AirdropWon","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"ID","type":"uint256"},{"indexed":false,"name":"hardDeadline","type":"uint256"}],"name":"RoundStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_type","type":"string"},{"indexed":false,"name":"_hash","type":"string"}],"name":"IPFSHashSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}]