Also I am not sure if I found a bug in how the total dividends (round mask) variable is tracked. Please correct me.

The Important Functions

Update game state (keys, airdrop, pot), and distribute dividends.

Update a state variable (round mask) that tracks how much dividend had been distributed.

The FOMO3D supply curve. Total supply of keys vs total supply ETH invested.

FOMO3D Supply Curve

Take the keys function and plot it directly:

FOMO Keys / ETH Invested

The supply curve is a square root function of the form:

(sqrt(keys * a + b^2) - b) / c

The reason for the constant “b” in the formula is to have a linear slope when invested ETH is 0.

The slope of the curve near 0 is:

a / (2 * b * c)

This constant approximates the early linear token supply growth. But in FOMO3D there’s an early ICO stage where everyone gets in at the same average price.

The code:

function keys(uint256 _eth) internal pure

returns(uint256) { return ((((((_eth).mul(1000000000000000000)).mul(312500000000000000000000000)).add(5624988281256103515625000000000000000000000000000000000000000000)).sqrt()).sub(74999921875000000000000000000000)) / (156250000); }

The slope at eth = 0 is 13333.3 keys / eth .

The number of keys generated when 1 eth is invested is a little bit less than the slope:

nkeys[1] / 1000000000000000000 => 13153.1

How Dividends Work (In Theory, I think)

When a new buyer comes in, some percentage of the newly invested ETH may be withdrawn by previous investors as dividends. Sending dividends to each player for each “buy” transaction would be too expensive. For this reason, FOMO uses a global state variable to track how much dividends are available in total ((“round mask”), and for each player also track how much dividends had been withdrawn so far (“the player mask”).

We have the following variables:

D = total dividends S = total shares s = player shares (for a player) dS = change in total shares dD = change in dividend ds = change in buyer's shares

Intuitively, a player’s entitled dividends are “total dividends per share” times “player’s shares”:

earning = (D / S) * s

If somebody else invests, there would be dD amount of new dividends, and also an increase in the supply of keys dS . The new profit per share is:

(D' / S') = (D + dD) / (S + dS)

And the total profit a player ought to be able to withdraw after the new dividends is:

(D' / S') * s

But a player may have withdrawn before, this fact should be stored in the “player mask” as (Dp / Sp) . To calculate how much additional dividends a player can withdraw:

dEarning = (D' / S') * s - (Dp / Sp) * s

In the case of the buyer (whose share of the key just increased), the dividends should also include the additional dividend generated from the current purchase:

dEarningIfBuyer = (dD / S') * dS + (D' / S') * s - (Dp / Sp) * s

Note that dS should not be retrospectively applied to Dp/Sp . In other words, the newly bought shares cannot partake prior dividends.

FOMO Dividends Implementation

The “round mask” is updated in the updateMasks function.

To calculate how much a player may withdraw, see calcUnMaskedEarnings:

function calcUnMaskedEarnings(uint256 _pID, uint256 _rIDlast)

private

view

returns(uint256) { return( (((round_[_rIDlast].mask).mul(plyrRnds_[_pID][_rIDlast].keys)) / (1000000000000000000)).sub(plyrRnds_[_pID][_rIDlast].mask) ); }

When a player DOES withdraw from dividend, the player mask is updated in updateGenVault:

plyrRnds_[_pID][_rIDlast].mask = _earnings.add(plyrRnds_[_pID][_rIDlast].mask);

Question: updateRoundMask

The code matches up with the theoretic equations, except for the updateRoundMask function.

The theoretic update function of round mask:

(D' / S') = (D + dD) / (S + dS)

The actual code implementation is different from the theoretic function:

uint256 _ppt = (_gen.mul(1000000000000000000)) / (round_[_rID].keys); round_[_rID].mask = _ppt.add(round_[_rID].mask);

The real code implements this recurrent function:

roundMask' = dD / S' + roundMask

According to the code, the mask is updated like this:

mask0 = dD0 / dS0 mask1 = dD1 / (dS0 + dS1) + dD0 / dS0

But theoretically it should update like this:

mask0Theoretic = (0 + dD0 / 0 + dS0) = (dD0 / dS0) mask1Theoretic = (dD1 + dD0) / (dS0 + dS1)

= dD1 / (dS0 + dS1) + dD0 / (dS0 + dS1)

It seems that the FOMO implementation would grant slightly more dividends than expected. Since dD0 / (dS0 + dS1) < dD0 / dS0 .

Is this a bug?

References

Source code on EtherScan:

https://etherscan.io/address/0xa62142888aba8370742be823c1782d17a0389da1#code

Source code on Gist:

https://gist.github.com/hayeah/20a1fa15d5f24c293e9961187f357dde