摘要:ether 買 //從不同地址進的, 第一個參數是推薦人標識, 第二個是選的 team buyXid(id, team) buyXaddr(addr, team) buyXname(name, team) valuts 買 //從不同地址進的, 第一個參數是推薦人標識, 第二個是選的 team, 第三個是根據 key 數量計算出來的 eth reLoadXid(id, team, eth) reLoadXaddr(addr, team, eth) reLoadXname(name, team, eth) buyCore // 這裏就是判斷了一下本輪是否結束了, 然後直接調用的 core,當然結束會走 endRound reLoadCore // 同上, 結束的判斷, 還有就是減去 gen 的金額, 再調用 core core // 限制前100eth, 更新 end 時間, 超過0.1eth 判斷空投, 更新玩家及輪次等數據, 調用2個分紅方法 distributeExternal // 給固定的13% (10% aff,2% com,1% otherFomo) 及 P3D 打錢 distributeInternal // 給空投1% 和 gen 和 pot 打錢 提現跑路 withdraw() 結束一輪 endRound() // pot 分成5分, win 拿48%, 2%給 com, 還有 gen, p3d, nextRound 則根據配置來分配, 其中 p3d 和下一輪邏輯比較簡單, 而 gen 我還沒太懂, 因爲涉及到 mask 的我都沒看明白( 沒時間細看, 全是數學, 要慢慢推理分析 ) 註冊 name //註冊一個 name 用於推廣獲取提成, 第一個參數是 name 標識, 第二個是推薦人的標識, 第三個是是否同步到其他遊戲 registerNameXID(name, id, all) registerNameXaddr(name, addr, all) registerNameXname(name, name, all) 玩家信息相關 , 前2個一般是給外部調用的 receivePlayerInfo //將傳入玩家信息儲存 receivePlayerNameList //儲存玩家的所有name determinePID //確定玩家信息, 若無則生成一個 pid 玩家分紅, keys相關 calcUnMaskedEarnings // 實現看不懂, 不過方法作用是用來計算能提現的收益 calcKeysReceived(rid, eth) // 根據輪次返回 用eth能買多少 keys iWantXKeys // 根據 key 數量返回需要多少 eth managePlayer // 第 x 輪時將上一輪的收益移至此輪, 僅輪次開始後第一次購買執行 updateGenVault // 計算及更新收益 updateMasks // 更新被鎖定的收益 withdrawEarnings // 計算可提現的收益。最後說下此類庫在 fomo 中的樣子: 首先 data 照舊, 而需要簽名的數量來自 teamJust.sol, 它的定義是構造是初始爲1, 以後每 add 一個 admin 或 dev 就把對應的 requiredSignatures 加一, remove 同理, 減一. 所以在部署時不改代碼的話, 只要滿足對應的身份限制, 加了這個MSFun.muitiSig 的方法默認是一個人調用就能執行。

Fomo3D 源自上古世紀, 今天我們來看看他的源碼 (實際上不是今天看的)

準備工作

環境準備 (用於調試合約)

  • git, nodejs, Chrome
  • ganache-cli, remix-ide

代碼 及 IDE

安裝好 Git 後, 下載源碼 git clone https://github.com/reedhong/fomo3d_clone.git
安裝好 nodejs 後, 使用 npm 安裝2個東西(建議使用國內鏡像源:cnpm) npm install ganache-cli -g & npm install remix-ide -g

至於IDE 上的選擇, 只要 IDE 支持 sol 語法, 如 idea 就有 solidity 插件, 亦或者 vscode 也很棒, 而且中文支持比較好, 還對於大文件 js 及 json 打開速度比較快, 編輯也比較流暢( idea 可能是插件太多, 各種語法解析比較卡)

源碼結構

+-- interface	 
|	 +-- DiviesInterface.sol
|	 +-- F3DexternalSettingsInterface.sol
|	 +-- HourglassInterface.sol
|	 +-- JIincForwarderInterface.sol
|	 +-- JIincInterfaceForForwarder.sol
|	 +-- PlayerBookInterface.sol
|	 +-- PlayerBookReceiverInterface.sol
|	 +-- TeamJustInterface.sol
|	 +-- otherFoMo3D.sol
+-- library	
|	 +-- F3DKeysCalcLong.sol
|	 +-- F3Ddatasets.sol
|	 +-- MSFun.sol	
|	 +-- NameFilter.sol
|	 +-- SafeMath.sol	
|	 +-- UintCompressor.sol
+-- Divies.sol
+-- F3Devents.sol	
+-- F3DexternalSettings.sol	
+-- FoMo3Dlong.sol	
+-- Hourglass.sol	
+-- JIincForwarder.sol
+-- PlayerBook.sol	
+-- TeamJust.sol	
+-- modularLong.sol

以上就是 reed 大佬整理的源碼結構, 看到這麼多文件, 心裏感覺好慌, 別怕, 其實大多數文件都是擺設, 沒有太多邏輯代碼, 我們主要需要看的, 也就是那麼幾個合約, 既然如此, 我們先排除一些用處不大, 非遊戲關鍵核心的合約

各大收款合約

  • JIincForwarder.sol (JIincForwarderInterface 類型變量的實際引用), 用於向項目基金會轉賬
  • otherFoMo3D.sol (遊戲 activate 前必須設置的 otherFomo 變量的實際引用), 向不知道哪個地址轉賬
  • Divies.sol (DeviesInterface 類型變量的實際引用), 用於 p3d 分紅

JIincForwarder.sol

這個合約就是向 基金會地址 轉發 ether, 單獨寫一箇中轉的好處就是靈活, 這個合約可以做到基金會地址安全轉移, 也就是說中途可以改變基金會的轉賬地址, 而這個過程需要新舊2個合約共同完成(舊.startMigration(新地址)--> 新.finishMigration(), 中途 舊方可以 舊.cancelMigration(), 而完成地址轉移後, 新地址完全替代舊地址 )

其中比較轉賬邏輯就是調用下面的這個接口對應的實際合約 的 deposit 方法

interface JIincInterfaceForForwarder {
    function deposit(address _addr) external payable returns (bool);
    function migrationReceiver_setup() external returns (bool);
}

至於現在這個基金會的地址到底是啥, 可以通過 status() 方法查看哦

otherFoMo3D.sol

這個合約很有意思, 或者說它的背後很有意思, 大家都想知道 其他的 fomo 到底是啥, 據說不是 soon 版

至於邏輯上, 這個 potSwap 的調用時機是在玩家買 key 的時候, 而它的作用, 我認爲是遊戲間的獎池交換

比如說, fomolong 共有100個 ether 買入, 那麼就會有1%流向 otherFomo 的獎池, 同理, otherFomo 裏應該也會有這個邏輯的存在, 這麼做有啥用就交給大家自己思考了

interface otherFoMo3D {
    function potSwap() external payable;
}

fomo3Dlong 代碼: (fomo3Dlong本身也可以是一個 otherFomo, 甚至在 真正的otherFomo 裏它的那個 otherFomo 就是 fomo3Dlong 也不一定)

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);
}

Divies.sol

這部分是給 P3D 分紅的, 代碼很簡單, 就一個轉賬的調用, 調用時機上, 首先是買 key 的錢被瓜分時, 有它的一份, 其次當一輪 (Round) 結束後, 又會根據贏的隊伍來分配獎池, 抽出一部分給到 P3D

interface DiviesInterface {
    function deposit() external payable;
}

當然這其中如何給 P3D 分紅我還沒搞太懂, 大致流程貌似是: 買 key分紅 --> 調用 Divies 的 deposit 方法, Divies 合約中此方法無具體實現(空方法, 啥也不幹, 就收錢) --> 預計什麼時候會有 P3D 的玩家來調用這個合約的 distribute 方法, 而 這個方法的作用似乎是將 分紅轉來的錢拿去投入 P3D, 然後賣出, 根據傳入的百分比決定是否繼續投入或重複投入和售出多少次, 最後把錢提現回來(可能就沒多少了), 而錢通過10% 的分紅機制全給了 P3D 的用戶??? 這一塊一直不太懂, 而且這個方法的 調用時機不明, 調用時還增加了 時間限制和擁擠隊列的限制. 總的將這裏面就是存在給 P3D 分紅的錢, 但這錢啥時候 給 P3D, 我還是沒猜出來.

3大合約

光是轉賬合約就感覺有些看不懂了, 真是頭疼啊, 只好把不懂的放下, 留待日後琢磨. 還是先分析遊戲核心代碼吧

  • TeamJust.sol
  • PlayerBook.sol
  • FoMo3Dlong.sol

TeamJust.sol

首先看 TeamJust.sol , 這個是用來做權限控制的, 裏面 除了與 muitiSig( 這個以後說 )相關的幾個方法, 也就是管理 admin 和 dev 了, 如 addAdmin removeAdmin , 而 isDev isAdmin 則是拿來給其他合約調用(比如 playerBook 的 onlyDevs)

function setup(address _addr)
   onlyDevs()
   public
{
   require( address(Jekyll_Island_Inc) == address(0) );
   Jekyll_Island_Inc = JIincForwarderInterface(_addr);
}

經過我的觀察發現, 這個 teamJust 合約應該是比較後加的, 比如 fomo3Dlong 合約的激活就沒有使用, 而2個合約不同的對於 Jekyll_Island_Inc 的賦值也讓我推測這可能是較新的寫法. 我也覺得這種通過調用合約賦值的方式比較好, 所以在我整的 項目 fomo3d_truffle 中, 我把 activate 函數的用戶限制 也改成了 用 teamJust 來做, 而 其中的 playerBook 和 teamJust 實際合約地址也是通過 類似上面 setup 的方式 賦值, 這麼做還有個好處就是可以通過 truffle 一鍵把這些合約部署且賦值, 而不是弄一個改源碼重新編譯這種測試起來比較麻煩的方式

PlayerBook.sol

這個合約主要是管理 玩家信息, 而玩家信息則分爲 name, id, addr, id 是根據地址是否存在自增生成的, 而 name 則是通過 花錢註冊可用於推廣獲取提成的! 合約內大多方法都像個數據庫一樣均爲 crud 操作, 夾帶的邏輯無非就是一些驗證, 其他的都比較少, 裏面比較有意思的點就是 addGame

function addGame(address _gameAddress, string _gameNameStr)
    onlyDevs()
    public
{
    require(gameIDs_[_gameAddress] == 0, "derp, that games already been registered");
    
    if (multiSigDev("addGame") == true)
    {deleteProposal("addGame");
        gID_++;
        bytes32 _name = _gameNameStr.nameFilter();
        gameIDs_[_gameAddress] = gID_;
        gameNames_[_gameAddress] = _name;
        games_[gID_] = PlayerBookReceiverInterface(_gameAddress);
    
        games_[gID_].receivePlayerInfo(1, plyr_[1].addr, plyr_[1].name, 0);
        games_[gID_].receivePlayerInfo(2, plyr_[2].addr, plyr_[2].name, 0);
        games_[gID_].receivePlayerInfo(3, plyr_[3].addr, plyr_[3].name, 0);
        games_[gID_].receivePlayerInfo(4, plyr_[4].addr, plyr_[4].name, 0);
    }
}

這裏是把 fomo3Dlong 的地址和名稱傳入, 然後就會通過接口向 fomo3Dlong 傳入幾個預設的玩家信息(來自 playerbook的構造方法), 而調用過這個方法後, registerNameXxxxFromDapp 這樣的方法才能不被 isRegisteredGame 攔截. 所以部署時, 這一步是必做的.

其他的幾個點: 可設置的註冊費用, 且費用被轉到基金會; 購買 key 邀請分紅總是和訪問的鏈接的推廣碼有關, 只有在無推廣碼時, 才從歷史中獲取 laff, 而 laff 每訪問一個推廣碼(並買了 key)都在改變

FoMo3Dlong.sol

主要合約啊, 先看下 所有的 state 變量

string constant public name = "FoMo3D Long Official";
string constant public symbol = "F3D";
uint256 private rndExtra_ = extSettings.getLongExtra();     // length of the very first ICO 
uint256 private rndGap_ = extSettings.getLongGap();         // length of ICO phase, set to 1 year for EOS.
uint256 constant private rndInit_ = 1 hours;                // round timer starts at this
uint256 constant private rndInc_ = 30 seconds;              // every full key purchased adds this much to the timer
uint256 constant private rndMax_ = 24 hours;                // max length a round timer can be  
uint256 public airDropPot_;             // person who gets the airdrop wins part of this pot
uint256 public airDropTracker_ = 0;     // incremented each time a "qualified" tx occurs.  used to determine winning air drop
uint256 public rID_;  

mapping (address => uint256) public pIDxAddr_;          // (addr => pID) returns player id by address
mapping (bytes32 => uint256) public pIDxName_;          // (name => pID) returns player id by name
mapping (uint256 => F3Ddatasets.Player) public plyr_;   // (pID => data) player data
mapping (uint256 => mapping (uint256 => F3Ddatasets.PlayerRounds)) public plyrRnds_;    // (pID => rID => data) player round data by player id & round id
mapping (uint256 => mapping (bytes32 => bool)) public plyrNames_; // (pID => name => bool) list of names a player owns.  (used so you can change your display name amongst any name you own)

mapping (uint256 => F3Ddatasets.Round) public round_;   // (rID => data) round data
mapping (uint256 => mapping(uint256 => uint256)) public rndTmEth_;      // (rID => tID => data) eth in per team, by round id and team id

mapping (uint256 => F3Ddatasets.TeamFee) public fees_;          // (team => fees) fee distribution by team
mapping (uint256 => F3Ddatasets.PotSplit) public potSplit_;     // (team => fees) pot split distribution by team

大部分都可以通過 變量名 猜出個大概, 實在不行可以搜索大致看一下哪裏用了, 結合的先看一下, 其他都是各種數據, 沒啥複雜的, 這裏就主要看下 fees_ 和 potSplit_

// 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
    
// how to split up the final pot based on which team was picked
// (F3D, P3D)
potSplit_[0] = F3Ddatasets.PotSplit(15,10);  //48% to winner, 25% to next round, 2% to com
potSplit_[1] = F3Ddatasets.PotSplit(25,0);   //48% to winner, 25% to next round, 2% to com
potSplit_[2] = F3Ddatasets.PotSplit(20,20);  //48% to winner, 10% to next round, 2% to com
potSplit_[3] = F3Ddatasets.PotSplit(30,10);  //48% to winner, 10% to next round, 2% to com

fees_ 就是用來決定 玩家 買 key 後, 買 key 的 ether 怎麼分配, 其中 2% 基金會(com) + 1% (otherFomo) + 1% 空投池 + fees_[].p3d % P3D + fees_[].gen % 收益, 10% 給 推薦人(無則給P3D)

總結就是 14% 固定 + 86% 可設定, 86% 分3塊( gen+p3d+pot ),所以2隊是56% gen + 10% p3d + 20% pot, 其他隊伍類似

potSplit_ 類似, 固定的 48%(win)+2%(com) + 50% 可設定, 分3塊(gen+p3d+nextround), 如2隊的 20 gen + 20 p3d + 10 next

然後講講所有的方法, 簡單的歸類下

修飾器
isActivated() 			//攔截遊戲未激活
isHuman()					//聽說攔截非人類?
isWithinLimits(eth)		//攔截太窮的人和 v 神 ???

ether 買		//從不同地址進的, 第一個參數是推薦人標識, 第二個是選的 team
buyXid(id, team)  	
buyXaddr(addr, team)
buyXname(name, team)

valuts 買		//從不同地址進的, 第一個參數是推薦人標識, 第二個是選的 team, 第三個是根據 key 數量計算出來的 eth
reLoadXid(id, team, eth)	
reLoadXaddr(addr, team, eth)
reLoadXname(name, team, eth)


buyCore  			// 這裏就是判斷了一下本輪是否結束了, 然後直接調用的 core,當然結束會走 endRound
reLoadCore		// 同上, 結束的判斷, 還有就是減去 gen 的金額, 再調用 core
core				// 限制前100eth, 更新 end 時間, 超過0.1eth 判斷空投, 更新玩家及輪次等數據, 調用2個分紅方法
distributeExternal 	// 給固定的13% (10% aff,2% com,1% otherFomo) 及 P3D 打錢
distributeInternal	// 給空投1% 和 gen 和 pot 打錢

提現跑路
withdraw() 	

結束一輪
endRound()	// pot 分成5分, win 拿48%, 2%給 com, 還有 gen, p3d, nextRound 則根據配置來分配, 其中 p3d 和下一輪邏輯比較簡單, 而 gen 我還沒太懂, 因爲涉及到 mask 的我都沒看明白( 沒時間細看, 全是數學, 要慢慢推理分析 )		

註冊 name		//註冊一個 name 用於推廣獲取提成, 第一個參數是 name 標識, 第二個是推薦人的標識, 第三個是是否同步到其他遊戲
registerNameXID(name, id, all) 
registerNameXaddr(name, addr, all)
registerNameXname(name, name, all)

玩家信息相關 , 前2個一般是給外部調用的
receivePlayerInfo			//將傳入玩家信息儲存
receivePlayerNameList		//儲存玩家的所有name
determinePID				//確定玩家信息, 若無則生成一個 pid

玩家分紅, keys相關
calcUnMaskedEarnings  		// 實現看不懂, 不過方法作用是用來計算能提現的收益
calcKeysReceived(rid, eth)	// 根據輪次返回 用eth能買多少 keys
iWantXKeys					// 根據 key 數量返回需要多少 eth
managePlayer					// 第 x 輪時將上一輪的收益移至此輪, 僅輪次開始後第一次購買執行
updateGenVault				// 計算及更新收益
updateMasks					// 更新被鎖定的收益
withdrawEarnings				// 計算可提現的收益

這麼多方法, 我也只能列個大致作用和我看的懂的邏輯, 具體的細節等我參透再出文章

最後總結遊戲大致邏輯 : 玩家買 key--> buyXxx(relaodXxx) 方法--> xxxCore --> core --> distributeExternal & distributeInternal --> 遊戲結束 --> 玩家 buy 觸發 endRound --> 分了錢 pot 的錢, 部分轉入下一輪 --> 激活新一輪 --> 接上最開始 進入循環 !!! 當然中途可以提現自己沒鎖住的收益, 以及註冊 name 拉人啥的.

幾個有意思的類庫

MSFun.sol

首先說下, 這個庫是用來做多重簽名的, 啥意思呢? 就是一個方法, 必須好幾個(多)人同意執行, 最後纔會執行. 用法如下:

*                                ┌────────────────────┐
 *                                │ Setup Instructions │
 *                                └────────────────────┘
 * (Step 1) import the library into your contract
 * 
 *    import "./MSFun.sol";
 *
 * (Step 2) set up the signature data for msFun
 * 
 *     MSFun.Data private msData;
 *                                ┌────────────────────┐
 *                                │ Usage Instructions │
 *                                └────────────────────┘
 * at the beginning of a function
 * 
 *     function functionName() 
 *     {
 *         if (MSFun.multiSig(msData, required signatures, "functionName") == true)
 *         {
 *             MSFun.deleteProposal(msData, "functionName");
 * 
 *             // put function body here 
 *         }
 *     }

大致就是先導包, 然後定義一個 MSFun.data 作爲區分合約的標識, 然後再方法中使用 if 包圍, if 第一句就是將之前的簽名清空

MSFun.multiSig( data標識, 需要簽名的數量, 方法名稱 )

最後說下此類庫在 fomo 中的樣子: 首先 data 照舊, 而需要簽名的數量來自 teamJust.sol, 它的定義是構造是初始爲1, 以後每 add 一個 admin 或 dev 就把對應的 requiredSignatures 加一, remove 同理, 減一. 所以在部署時不改代碼的話, 只要滿足對應的身份限制, 加了這個MSFun.muitiSig 的方法默認是一個人調用就能執行

SafeMath.sol

這個沒啥好說的, 操作金額必備, 聽說狼人殺就是少了這個被攻擊的(整形溢出), 也許可以不懂怎麼攻擊, 但一定要懂怎麼防範, so

/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) 
    internal 
    pure 
    returns (uint256 c) 
{
    if (a == 0) {
        return 0;
    }
    c = a * b;
    require(c / a == b, "SafeMath mul failed");
    return c;
}

如你所見, 簡單的判斷即可確保不會由於溢出導致數據錯亂

F3DKeysCalcLong.sol

我只能猜到作用, 至於完全理解... 沒上過大學的我瑟瑟發抖

keysRec(curEth, newEth)			// 第一個參數就是using 後的調用方, 第二個參數是 準備花的 eth, 如我花0.01 eth , 用 round_[rId].eth.keysRec(0.01 eth); 得出的就是當前輪次時0.01eth 能買多少個 key, 注意返回的 keys 很大, 1個 實際上是 1e18 吧, 
ethRec(curKeys, sellKeys)		// 同上, 輸入想買的 keys 數量, 返回當前輪次 keys 基數下購買 keys 需要花的 eth
keys(eth)						// 根據 eth 計算可得多少 keys
eth(keys)						// 根據 keys 計算需要多少 eth

bundle.js 中, iWantKeys 邏輯

count = BN(parseInt(count) * 1e18)
let priceQuotation = await JUST.Bridges.Browser.contracts.Quick.read('iWantXKeys', count)

keys 和 eth 應該是對應的, 而 eth 的變化規律如果畫圖的話應該是 指數級上升? 可以畫成函數看看

本文參與 深入淺出區塊鏈寫作激勵計劃 ,歡迎正在閱讀的你也加入。

  • 發表於 42分鐘前
  • 閱讀 ( 8 )
  • 學分 ( 0 )
  • 分類:DApp
相關文章