在 Fomo3D 遊戲中的中主合約 FoMo3dlong,具體代碼見 :
https://etherscan.io/address/0xa62142888aba8370742be823c1782d17a0389da1
存在訪問一個外部接口的數據
uint256 private rndExtra_= extSettings.getLongExtra();
uint256 private rndGap_= extSettings.getLongGap();
非常可惜,對於 extSettings 的合約地址:
https://etherscan.io/address/0x32967d6c142c2f38ab39235994e2ddf11c37d590
是一個閉源合約,無法看到其中的實現過程,直接讀取接口的數據,返回值均爲 0。在實現上應該設計有訪問控制。當然此處,可以分析合約的 OpCode,逆向到合約代碼。但難度較高,非我所擅長,只能另闢蹊徑。
當然也可以根據整個程序的邏輯,大致寫死一個數值。但如果還是想得到真正的參數數據改怎麼辦呢?苦心研究一天多,發現兩種不錯的方案,可以獲取正確的值。
方案一:代碼分析法
關鍵代碼分析
在 FoMo3dlong 的 activate 中有一段代碼
round_[1].strt = now + rndExtra_- rndGap_;
round_[1].end = now + rndInit_+ rndExtra_;
我們在鏈上非常容易獲取到本輪的 strt, end 的值。兩行代碼相當於兩個方程式,我們獲取到 now 值,我們就相當於解一個簡單的方程式,就可以破解到。那麼如何獲取到 now 值呢?
now 值的獲取
對於合約編程比較熟悉的同學很容易判斷出,now 值的設定就是調用 activate() 方式之時。那麼我們問題的關鍵就在於找到合約什麼時候執行 activate() 的方法
activate 方法調用。
我們通讀整個 FoMo3dlong 的實現,很容易得到下面的一個判斷,此合約在部署完成之後,一定需要做兩件事情:
1. 調用 setOtherFomo() 方法,設定另外一個遊戲玩法的合約
2. 調用 activate() 方法,激活合約,真正啓動遊戲。
那我們就要回到合約:
https://etherscan.io/address/0x32967d6c142c2f38ab39235994e2ddf11c37d590
源頭,看合約的創建是什麼時候開始的,然後依次看最近幾筆交易,肯定能發現所要的答案。很簡單我們只需要瀏覽 etherscan.io 上該合約所有的交易數據
https://etherscan.io/txs?a=0xa62142888aba8370742be823c1782d17a0389da1
非常可惜,etherscan.io 只展示最新的 10 萬筆交易,最早的交易沒了!
上圖看我此時的心情!
但我們是不會輕易放棄的人。繼續 google,查其它的以太坊瀏覽器。功夫不負有心人,找到一個能瀏覽合約所有交易情況的瀏覽器。
https://blockchair.com/ethereum/address/0xa62142888aba8370742be823c1782d17a0389da1
但交易太多,沒有翻頁功能,點擊到手軟,也很弄不出來,該網站的用戶體驗實在是太差。怎麼辦呢 ? 幸虧頁面的下面提供按照區塊篩選的功能。
一直追蹤,在下面的訪問地址上,看到了最原始的合約場景
https://blockchair.com/ethereum/calls?q=recipient(0xa62142888 geaba8370742be823c1782d17a0389da1),block_id(5932000..5910006)&s;=block_id(asc)
看第一筆,一個紅紅的 create 讓我看到了希望。把交易信息放在 etherscan 上
https://etherscan.io/tx/0xf63e775e10b0f662574ab49cd4c080ddcda8ca7d0012b5f0fbf0b03ad1c977ac
這筆交易創造出風靡一時的 Fomo3d。我們可以圍觀出很多信息:
1. 合約的第一次部署在 7 月 6 號,25 天前
2. 合約部署消耗掉了 600w+的 gas。超過不少測試網和私鏈默認的區塊 gaslimit,會出現很多部署失敗的情況。
3. 部署的時候 gasPrice 達到 82.5Gwei,整個花掉了 0.5ETH 的費用。估計當時以太坊相當堵。
順着看交易詳情,傻了!這個網站有 BUG,把不屬於這個合約的交易都歸類下面。硬着頭皮瀏覽了五六筆交易的詳情找到:
點擊右小腳,通過 etherscan.io 的鏈接:
https://etherscan.io/tx/0x422728d092a8237a8a0544274c7268d0f1daf598c43f3e0d403c787f57a32be3
終於發現 setOtherFomo 的調用。
通過這筆交易我們可以也可以觀察到不少有趣的事情:
1. 此處設置的合約地址,並不是 Fomo3dSoon 的合約地址。
設置的合約地址:
https://etherscan.io/address/0xf39e044e1ab204460e06e87c6dca2c6319fc69e3
Fomo3dSoon 的合約地址:
https://etherscan.io/address/0x4e8ecF79AdE5e2C49 B9e30D795517A81e0 Bf00 B8
2. 通過 FoMo3dlong 的代碼可以看出,用戶下注有 1% 的錢都到這邊來了,具體可以看到交易信息。可見:
https://etherscan.io/txsInternal?a=0xf9ba0955b0509ac6138908ccc50d5bd296e48d7d&p;=1
3. 到目前爲止,這筆錢都在這個被廢棄的合約上,大概接近 1000 個 ETH。這筆錢最終怎麼分?依照現有的邏輯,很大一部分會永遠地鎖死在合約上。但也有可能開發團隊有提幣的接口,可以被直接提走。
本以後順着這筆交易往後,就能看到 activate 的調用。可惜網站的 bug,導致連續很多的交易都跟 f3d 合約沒關係。只好放棄這條線索。
怎麼辦呢?
找其他相關性來解決。回過頭來在看這筆交易:
https://etherscan.io/tx/0x422728d092a8237a8a0544274c7268d0f1daf598c43f3e0d403c787f57a32be3
其 from 地址爲:
https://etherscan.io/address/0xf39e044e1ab204460e06e87c6dca2c6319fc69e3
既然此地址調用 setOtherFomo 那我們有理由猜測,activate 的也是該地址所調用。我們直接查看這個地址上所有的交易,我們終於發現一筆交易:
https://etherscan.io/tx/0xc6bd9c1e882064c55435227b9999ff4fb66a00f6572d5cfd3eb92b4a2c72d10e
此處應該有掌聲!
到此爲止,我們就可以根據次合約的執行時間,去設置 now 的正確值。此處涉及 utc 轉北京時間,北京時間轉 unix timestamp 等各種複雜計算,就不一一列出。最終的計算值是:
round_[1].strt = now + rndExtra_- rndGap_;
round_[1].end = now + rndInit_+ rndExtra_;
在看上面的公式,我們有了 now 值,rndInit_ 是一個小時,strt 值合約上能直接讀取到:1531080612。我用 parity 截圖如下:
那 end 怎麼辦呢?有兩種方法:
-
猜一個大致的值,比如 start 過後 2 個小時
-
回溯區塊鏈到高度 5929395,獲取那時候的具體的 end 值
後一種可以具體寫程序實現,後續我會提供。
方案二:程序直接獲取
此方案曾在年初分析以太貓的程序中用到過,大家讀取一篇文章:
https://medium.com/aigang-network/how-to-read-ethereum-contract-storage-44252c8af925
即可明白原理。依據上面分析 , 正對 Fomo3dlong 的合約,我們寫了下面的分析代碼如下:
var Web3 = require(\”web3\”);
// 創建 web3 對象
var web3 = new Web3();
// 連接到以太坊節點
web3.setProvider(new Web3.providers.HttpProvider(\”http://localhost:8545\”));
let contractAddress = \’0xA62142888ABa8370742bE823c1782D17A0389Da1\’
async function asyncPrint(index) {
var p=web3.eth.getStorageAt(contractAddress, index); var value; try { value = await p; } catch (err) { console.error(err); } console.log(`[${index}]` + value); } for (index = 0; index < 10; index++){ asyncPrint(index); }
}
我們設置 FoMo3dlong 的合約地址,分析前 10 個變量的值。用 node 直接運行,最終的輸入結果如下:
[0]0x000000000000000000000000f9ba0955b0509ac6138908ccc50d5bd296e48d7d
[1]0x000000000000000000000000000000000000000000000000000000000000000f
[2]0x0000000000000000000000000000000000000000000000000000000000000e10
[3]0x000000000000000000000000000000000000000000000000042e0a1276315c92
[4]0x0000000000000000000000000000000000000000000000000000000000000000
[5]0x0000000000000000000000000000000000000000000000000000000000000001
[6]0x0000000000000000000000000000000000000000000000000000000000000000
[7]0x0000000000000000000000000000000000000000000000000000000000000000
[8]0x0000000000000000000000000000000000000000000000000000000000000000
[9]0x0000000000000000000000000000000000000000000000000000000000000000
對比 FoMo3dlong 的合約代碼,去掉其中的常量(在不同的數據區),我們可以得出一個映射關係 (注意:打印出來的值是 16 進制的):
[0]—>otherF3D_(f9ba0955b0509ac6138908ccc50d5bd296e48d7d)
[1]—>rndExtra_(f)
[2]—>rndGap_ (e10)
[3]—>airDropPot_(42e0a1276315c92)
[4]—>airDropTracker_(0)
[5]—>rID_(1)
爲了驗證準確性,我們可以檢查 0,3,4,5 四個字段的值。
1. 字段 [0]:
otherF3D_ 的值也符合我們在方案一中找到的合約地址:
https://etherscan.io/address/0xf39e044e1ab204460e06e87c6dca2c6319fc69e3
2. 字段 [4],[5] 也符合我們合約上看到的值。
3. 字段 [3],airDropPot_ 非常具有代表意義,這個值隨着用戶的下注在變化,我們可以實時刷新對比一下真正鏈上獲取的數據,非常一致。
據此我們可以非常自信地判斷:
rndExtra_= 15 seconds;
rndGap_= 1 hours; //(e10=3600)
我們可以直接寫死這兩個數值,也可以做一個合約,實現相應的接口。
總結
1. 方案一雖然繁瑣,但其流程,非常值得研究,需要深入理解以太坊合約運行的內在機制方可找到一些線索。
2. 此文信息量非常大,希望能深入研究,動手做一遍流程。學會深度使用以太坊的區塊瀏覽器,學會如何用代碼跟合約就行交互,理解以太坊的運作機制和合約開發的底層邏輯。
我們有專門的羣討論區塊鏈遊戲開發,添加下面的微信,可付費入羣。