主要类结构
otherFomo3D,收益分发的接口,1% to pot swap。
PlayerBookInterface,游戏玩家信息的统一管理接口。
F3DexternalSettingsInterface,游戏启动参数设置接口。
JIincForwarderInterface,开发社区收益分发的接口,2% to com。
DiviesInterface, p3d收益分发的接口。
核心流程分析
这里只分析两类核心流程,购买Key与领取收益。
购买Key
fallback function
默认战队:2,即为蛇战队
function() isActivated() isHuman() isWithinLimits(msg.value) public payable
buyXid、buyXaddr、buyXname三个方法除了多一些推荐人与战队设置相关的处理,其他逻辑与function()逻辑一致,最终入口是buyCore。
function buyCore(uint256 _pID, uint256 _affID, uint256 _team, F3Ddatasets.EventReturns memory _eventData_)
private
还有一种是根据收益购买Key,主要包括reLoadXid、reLoadXaddr、reLoadXname三个方法,计算收益并复购Key。
buyCore核心调用流程:
early round eth limiter是为了早期限制,当该轮游戏参与的ETH低于100ETH时,每个人限购1ETH。
在核心调用流程里面,主要涉及到Key的计算、游戏时间更新、空投奖励、玩家与游戏轮次信息更新以及大家最关注最核心的eth的分发,中间涉及到日志的处理。
- Key的相关计算
根据该轮游戏当前的Eth和购买Key的Eth计算可以购买Key的数量。
/**
* @dev calculates number of keys received given X eth
* @param _curEth current amount of eth in contract
* @param _newEth eth being spent
* @return amount of ticket purchased
*/
function keysRec(uint256 _curEth, uint256 _newEth)
internal
pure
returns (uint256)
{
return(keys((_curEth).add(_newEth)).sub(keys(_curEth)));
}
/**
* @dev calculates how many keys would exist with given an amount of eth
* @param _eth eth "in contract"
* @return number of keys that would exist
*/
function keys(uint256 _eth)
internal
pure
returns(uint256)
{
return ((((((_eth).mul(1000000000000000000)).mul(312500000000000000000000000)).add(5624988281256103515625000000000000000000000000000000000000000000)).sqrt()).sub(74999921875000000000000000000000)) / (156250000);
}
游戏时间更新
当购买的Key大于等于1整个的时候,更新游戏时间,更新该轮游戏最新的游戏玩家为当前玩家,更新该轮游戏最新的战队为当前玩家所属的战队。空投奖励
当参与购买的eth大于等于0.1ETH的时候(同时空投参数airDropTracker_加1),是可以参与空投奖励游戏,根据一些参数计算seed(0-999),如果seed < airDropTracker_,即获得空头奖励(同时airDropTracker_清零),该奖励根据参与购买的ETH数量相关,分三个奖励等级。
注意:这里可以在某些情况下增大赢取空头奖励的概率,因为一些参数并不是完全随机。
空投seed计算所在的方法:
function airdrop()
private
view
returns(bool)
{
uint256 seed = uint256(keccak256(abi.encodePacked(
(block.timestamp).add
(block.difficulty).add
((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
(block.gaslimit).add
((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
(block.number)
)));
if((seed - ((seed / 1000) * 1000)) < airDropTracker_)
return(true);
else
return(false);
}
空投的三个奖励等级:
if (airdrop() == true)
{
// gib muni
uint256 _prize;
if (_eth >= 10000000000000000000)// _eth >= 10eth
{
// calculate prize and give it to winner
_prize = ((airDropPot_).mul(75)) / 100;//空投奖励总量的75%
plyr_[_pID].win = (plyr_[_pID].win).add(_prize);
// adjust airDropPot
airDropPot_ = (airDropPot_).sub(_prize);
// let event know a tier 3 prize was won
_eventData_.compressedData += 300000000000000000000000000000000;
} else if (_eth >= 1000000000000000000 && _eth < 10000000000000000000) {//1eth <= _eth < 10eth
// calculate prize and give it to winner
_prize = ((airDropPot_).mul(50)) / 100;//空投奖励总量的50%
plyr_[_pID].win = (plyr_[_pID].win).add(_prize);
// adjust airDropPot
airDropPot_ = (airDropPot_).sub(_prize);
// let event know a tier 2 prize was won
_eventData_.compressedData += 200000000000000000000000000000000;
} else if (_eth >= 100000000000000000 && _eth < 1000000000000000000) {//0.1eth <=_eth < 1eth
// calculate prize and give it to winner
_prize = ((airDropPot_).mul(25)) / 100;//空投奖励总量的25%
plyr_[_pID].win = (plyr_[_pID].win).add(_prize);
// adjust airDropPot
airDropPot_ = (airDropPot_).sub(_prize);
// let event know a tier 3 prize was won
_eventData_.compressedData += 300000000000000000000000000000000;
}
// set airdrop happened bool to true
_eventData_.compressedData += 10000000000000000000000000000000;
// let event know how much was won
_eventData_.compressedData += _prize * 1000000000000000000000000000000000;
// reset air drop tracker
airDropTracker_ = 0;
}
- 玩家与游戏轮次信息更新
// update player
plyrRnds_[_pID][_rID].keys = _keys.add(plyrRnds_[_pID][_rID].keys);//该轮游戏玩家所拥有的总的keys
plyrRnds_[_pID][_rID].eth = _eth.add(plyrRnds_[_pID][_rID].eth);//该轮游戏玩家的参与的总的eth
// update round
round_[_rID].keys = _keys.add(round_[_rID].keys);//该轮游戏的总keys
round_[_rID].eth = _eth.add(round_[_rID].eth);//该轮游戏的总eth
rndTmEth_[_rID][_team] = _eth.add(rndTmEth_[_rID][_team]);//该轮游戏的战队的总eth
-
大家最关注最核心的eth的分发
分为distributeExternal和distributeInternal两类。
分发规则:
// Team allocation structures
// 0 = whales
// 1 = bears
// 2 = sneks
// 3 = bulls
// Team allocation percentages
// (F3D, P3D) + (Pot , Referrals, Community)
// Referrals / Community rewards are mathematically designed to come from the winner's share of the pot.
fees_[0] = F3Ddatasets.TeamFee(30,6); //50% to pot, 10% to aff, 2% to com, 1% to pot swap, 1% to air drop pot
fees_[1] = F3Ddatasets.TeamFee(43,0); //43% to pot, 10% to aff, 2% to com, 1% to pot swap, 1% to air drop pot
fees_[2] = F3Ddatasets.TeamFee(56,10); //20% to pot, 10% to aff, 2% to com, 1% to pot swap, 1% to air drop pot
fees_[3] = F3Ddatasets.TeamFee(43,8); //35% to pot, 10% to aff, 2% to com, 1% to pot swap, 1% to air drop pot
distributeExternal分发
贡献2%给开发社区。
// pay 2% out to community rewards
uint256 _com = _eth / 50;
uint256 _p3d;
if (!address(Jekyll_Island_Inc).call.value(_com)(bytes4(keccak256("deposit()"))))
{
// This ensures Team Just cannot influence the outcome of FoMo3D with
// bank migrations by breaking outgoing transactions.
// Something we would never do. But that's not the point.
// We spent 2000$ in eth re-deploying just to patch this, we hold the
// highest belief that everything we create should be trustless.
// Team JUST, The name you shouldn't have to trust.
_p3d = _com;
_com = 0;
}
贡献1%给Fomo3D short。
// pay 1% out to FoMo3D short
uint256 _long = _eth / 100;
otherF3D_.potSwap.value(_long)();
贡献10%给推荐人,如果推荐人不存在,则给p3d
// distribute share to affiliate
uint256 _aff = _eth / 10;
// decide what to do with affiliate share of fees
// affiliate must not be self, and must have a name registered
if (_affID != _pID && plyr_[_affID].name != '') {
plyr_[_affID].aff = _aff.add(plyr_[_affID].aff);
emit F3Devents.onAffiliatePayout(_affID, plyr_[_affID].addr, plyr_[_affID].name, _rID, _pID, _aff, now);
} else {
_p3d = _aff; //这里注意,如果前面的!address(Jekyll_Island_Inc).call.value(_com)(bytes4(keccak256("deposit()")))调用失败,则_p3 = _com,这部分eth会直接损失
}
按照战队的分成比例贡献给p3d
// pay out p3d
_p3d = _p3d.add((_eth.mul(fees_[_team].p3d)) / (100));
if (_p3d > 0)
{
// deposit to divies contract
Divies.deposit.value(_p3d)();
// set up event data
_eventData_.P3DAmount = _p3d.add(_eventData_.P3DAmount);
}
distributeInternal分发
/**
* @dev distributes eth based on fees to gen and pot
*/
function distributeInternal(uint256 _rID, uint256 _pID, uint256 _eth, uint256 _team, uint256 _keys, F3Ddatasets.EventReturns memory _eventData_)
private
returns(F3Ddatasets.EventReturns)
{
// calculate gen share
uint256 _gen = (_eth.mul(fees_[_team].gen)) / 100;//F3D部分
// toss 1% into airdrop pot 1%空投奖励
uint256 _air = (_eth / 100);
airDropPot_ = airDropPot_.add(_air);
// update eth balance (eth = eth - (com share + pot swap share + aff share + p3d share + airdrop pot share))
_eth = _eth.sub(((_eth.mul(14)) / 100).add((_eth.mul(fees_[_team].p3d)) / 100));//剩余部分为F3D部分与pot奖池部分
// calculate pot 奖池部分
uint256 _pot = _eth.sub(_gen);
// distribute gen share (thats what updateMasks() does) and adjust
// balances for dust.
uint256 _dust = updateMasks(_rID, _pID, _gen, _keys);
if (_dust > 0)//分发该轮游戏的Key分红奖励后可能会剩余一点
_gen = _gen.sub(_dust);
// add eth to pot
round_[_rID].pot = _pot.add(_dust).add(round_[_rID].pot);
// set up event data
_eventData_.genAmount = _gen.add(_eventData_.genAmount);
_eventData_.potAmount = _pot;
return(_eventData_);
}
/**
* @dev updates masks for round and player when keys are bought
* @return dust left over
*/
function updateMasks(uint256 _rID, uint256 _pID, uint256 _gen, uint256 _keys)
private
returns(uint256)
{
/* MASKING NOTES
earnings masks are a tricky thing for people to wrap their minds around.
the basic thing to understand here. is were going to have a global
tracker based on profit per share for each round, that increases in
relevant proportion to the increase in share supply.
the player will have an additional mask that basically says "based
on the rounds mask, my shares, and how much i've already withdrawn,
how much is still owed to me?"
*/
// calc profit per key & round mask based on this buy: (dust goes to pot)
uint256 _ppt = (_gen.mul(1000000000000000000)) / (round_[_rID].keys);//F3D分红部分对每Key的增加收益
round_[_rID].mask = _ppt.add(round_[_rID].mask);//该轮次的每Key收益增加
// calculate player earning from their own buy (only based on the keys
// they just bought). & update player earnings mask
uint256 _pearn = (_ppt.mul(_keys)) / (1000000000000000000);
plyrRnds_[_pID][_rID].mask = (((round_[_rID].mask.mul(_keys)) / (1000000000000000000)).sub(_pearn)).add(plyrRnds_[_pID][_rID].mask);//用户买了之后自己参与的部分应该算做已领取的分红Key收益
// calculate & return dust 如果不能整除,就有余数,这一部分收益返回归pot奖池所有
return(_gen.sub((_ppt.mul(round_[_rID].keys)) / (1000000000000000000)));
}
领取收益
玩家的收益主要来源包括:空投奖金、赢取的奖金、最终的战队分红、Key分红、推荐奖励、其它原因导致的eth退回。
收益的计算核心逻辑:
function withdrawEarnings(uint256 _pID)
private
returns(uint256)
{
// update gen vault
updateGenVault(_pID, plyr_[_pID].lrnd);
// from vaults
uint256 _earnings = (plyr_[_pID].win).add(plyr_[_pID].gen).add(plyr_[_pID].aff);//收益为赢取的奖金 + gen vault + 推荐奖励
if (_earnings > 0)
{
plyr_[_pID].win = 0;
plyr_[_pID].gen = 0;
plyr_[_pID].aff = 0;
}
return(_earnings);
}
function updateGenVault(uint256 _pID, uint256 _rIDlast)
private
{
uint256 _earnings = calcUnMaskedEarnings(_pID, _rIDlast);//计算Key分红收益
if (_earnings > 0)
{
// put in gen vault
plyr_[_pID].gen = _earnings.add(plyr_[_pID].gen);//Key分红收益添加到plyr_[_pID].gen,存储gen vault
// zero out their earnings by updating mask
plyrRnds_[_pID][_rIDlast].mask = _earnings.add(plyrRnds_[_pID][_rIDlast].mask);//更新玩家已经领取的Key分红收益
}
}
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) );//玩家Key分红收益 = 该轮游戏的每Key收益 X 玩家该轮游戏所拥有的Key数 - 玩家该轮已经领取的Key分红收益。
}
合约风险
function potSwap()
external
payable
{
// setup local rID
uint256 _rID = rID_ + 1;
round_[_rID].pot = round_[_rID].pot.add(msg.value);
emit F3Devents.onPotSwapDeposit(_rID, msg.value);
}