雖然相比其他互聯(lián)網(wǎng)技術(shù),智能合約等區(qū)塊鏈技術(shù)相對安全,但是并非絕對安全,即使是零漏洞的合約也有可能被竊取的私鑰劫持。先前的Bancor 和KICKICO黑客事件表明:攻擊者可以損害智能合約錢包。在這些攻擊中,即使合約具備可升級性機(jī)制,也可能無法修復(fù)已部署的智能合約。唯一的解決辦法是重新部署并正確初始化新的合約實(shí)例,以便為用戶恢復(fù)功能。
因此,所有智能合約開發(fā)者必須在合約設(shè)計(jì)階段整合一個(gè)遷移程序。此外,企業(yè)必須做好在合約損害事件發(fā)生時(shí)實(shí)施遷移的準(zhǔn)備。
遷移過程有兩個(gè)步驟:
1、恢復(fù)要遷移的數(shù)據(jù)
2、將數(shù)據(jù)寫入新合約
具體操作如下:
第一步:數(shù)據(jù)恢復(fù)
你需要從區(qū)塊鏈的某個(gè)特定區(qū)塊中讀取數(shù)據(jù)。要想從損害事件(黑客攻擊或故障)中恢復(fù)數(shù)據(jù),你需要在事件發(fā)生之前使用這個(gè)區(qū)塊,或者過濾攻擊者的操作。
如果可以的話,請暫停合約。這對于用戶來說更加透明公平,并能阻止攻擊者盯上那些對遷移不知情的用戶。
數(shù)據(jù)恢復(fù)的具體操作取決于你的數(shù)據(jù)結(jié)構(gòu)。
對于簡單類型的公共變量(public variables,例如 uint 或 address)來說,通過它們的getter來檢索特定值就可以了。而對于私有變量(private variables),你可以依賴事件,也可以計(jì)算變量的內(nèi)存偏移量,然后使用 getStorageAt[4]函數(shù)檢索它的值。
由于元素的數(shù)量是已知的,因此數(shù)組也很容易恢復(fù)。
至于映射(mappings)的話,情況有點(diǎn)復(fù)雜。由于鍵(Keys)在映射過程中不會(huì)被存儲(chǔ),所以你需要將它們進(jìn)行恢復(fù)才能訪問對應(yīng)的值(Values)。為了簡化鏈下追蹤的過程,我們建議在值被存儲(chǔ)在映射中時(shí)觸發(fā)事件(emit
events)。
在ERC20代幣合約中,你可以通過追蹤代幣的Transfer事件的地址來獲取代幣持有者列表。這個(gè)過程很難。
對此,我們準(zhǔn)備了兩個(gè)幫助方案:第一,你可以掃描區(qū)塊鏈并自行檢索持有者;第二,你可以依靠以太坊區(qū)塊鏈的公開Goog
BigTable存檔。
如果你不熟悉web3 API,無法從區(qū)塊鏈中提取信息。那么你可以使用ethereum-etl ,其提供了一系列腳本來簡化數(shù)據(jù)提取的過程。
如果你沒有已經(jīng)完成同步的區(qū)塊鏈,那么你可以使用Google BigQuery
API。圖1展示了如何通過BigQuery來收集某個(gè)特定代幣的所有地址:

圖1:利用Google BigQuery來恢復(fù)那些與在0x41424344這個(gè)地址中的代幣相關(guān)聯(lián)的Transfer事件的所有地址
BigQuery提供對區(qū)塊號(hào)的訪問,因此你可以將查詢結(jié)果調(diào)整為返回特定區(qū)塊的交易。
一旦你恢復(fù)了所有代幣持有者的地址,你就可以離線查詢balanceOf函數(shù) 以恢復(fù)與每個(gè)持有者相關(guān)的余額,同時(shí)過濾余額為零的帳戶。
現(xiàn)在我們知道如何檢索將要遷移的數(shù)據(jù),接下來我們要將數(shù)據(jù)寫入新合約。
第二步:數(shù)據(jù)寫入
完成數(shù)據(jù)收集后,你需要開啟新合約。
對于簡單變量,你可以通過合約的構(gòu)造函數(shù)來設(shè)置相應(yīng)的值。
如果你的數(shù)據(jù)無法保存在單筆交易中,那么情況會(huì)有點(diǎn)復(fù)雜,成本也會(huì)略高。每筆交易都包含在某個(gè)區(qū)塊中,該區(qū)塊限制了其交易可以使用的gas總量(即所謂的
GasLimit)。如果某筆交易的 gas
成本接近或超過此限制,那么礦工將不會(huì)將其打包進(jìn)該區(qū)塊內(nèi)。因此,如果想要遷移大量數(shù)據(jù),那么你必須將數(shù)據(jù)遷移拆分成多筆交易。
這類情況的解決方案是:在合約中添加初始化狀態(tài),只有合約擁有者才能更改狀態(tài)變量,并且用戶無法執(zhí)行任何操作。
對于ERC20代幣,上述過程將需要以下步驟:
1、在初始化狀態(tài)下部署合約,
2、遷移余額,
3、將合約的狀態(tài)移至生產(chǎn)狀態(tài)。
初始化狀態(tài)可以通過使用OpenZeppelin提供的 Pausable 功能和指示初始化狀態(tài)的布爾值(boolean)來實(shí)現(xiàn)。
為了降低成本,我們可以使用batchTransfer(批量傳輸)函數(shù)(該函數(shù)允許你在單筆交易中設(shè)置多個(gè)帳戶)來實(shí)現(xiàn)余額的遷移:

圖2:batchTransfer 函數(shù)示例
建議
在合約部署之前做好遷移程序的功課。
使用事件(events)來提高數(shù)據(jù)追蹤的效率。
如果你想要部署可升級合約,那么你必須準(zhǔn)備好遷移程序,因?yàn)槟愕拿荑€可能會(huì)受到損害,或者你的合約可能會(huì)受到錯(cuò)誤且不可逆轉(zhuǎn)的操縱。
智能合約帶來了新的開發(fā)范式——其不可變性要求用戶重新思考搭建應(yīng)用的方式,并且需要更透徹全面的設(shè)計(jì)和開發(fā)過程。
作者:Trail of Bits Blog
翻譯:喏唄爾
來源:Unitimes