Fomo3D 合約源碼淺析
摘要: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