開發(fā)人員最難掌握的Web3.0概念
這是大多數(shù)新的 Web 3.0 開發(fā)人員難以理解的事情:當(dāng)您發(fā)布代碼時(shí),以后無法對其進(jìn)行修補(bǔ)和更新。這與我們都知道和喜愛的迭代開發(fā)方法有很大的不同——我們將開發(fā)分解成更小的部分,并通過多次迭代來構(gòu)建它。通過逐一添加功能,我們可以隨著時(shí)間的推移改進(jìn)應(yīng)用功能——同時(shí)擴(kuò)大用戶群。在 Web 3.0 中,這是無法做到的。
迭代開發(fā)讓我們分階段構(gòu)建
稍后我們將討論 Web 3.0 的瀑布方法。但首先,需要了解迭代開發(fā)的入門知識。以這種方式構(gòu)建應(yīng)用程序有幾個(gè)優(yōu)點(diǎn):
1) 及早發(fā)現(xiàn)錯(cuò)誤
通過分階段構(gòu)建軟件,我們可以逐段構(gòu)建、測試和交付代碼。這讓我們可以在早期階段發(fā)現(xiàn)錯(cuò)誤并在進(jìn)行時(shí)修復(fù)它們,避免下游后果,例如影響整個(gè)應(yīng)用程序的一些小錯(cuò)誤。
2)更快的用戶反饋
由于我們以迭代方式發(fā)布應(yīng)用程序,迭代開發(fā)讓我們能夠快速、循環(huán)地獲得反饋。這不僅讓我們能夠進(jìn)行必要的錯(cuò)誤修復(fù),而且還幫助我們根據(jù)用戶的實(shí)時(shí)反饋制定未來的路線圖。
3) 減少時(shí)間計(jì)劃
迭代開發(fā)為我們節(jié)省了編寫復(fù)雜文檔的時(shí)間,因此我們可以花更多的時(shí)間構(gòu)建和測試,而減少編寫和理論化的時(shí)間。
但這在 Web3.0中發(fā)生了變化
當(dāng)然,區(qū)塊鏈改變了一切——包括迭代開發(fā)的可能性。大多數(shù)新的軟件開發(fā)人員得知這一點(diǎn)后都會感到震驚,但是一旦您掌握了智能合約的工作原理,就說得通了。
我們以以太坊區(qū)塊鏈為例。(如果您需要入門,請閱讀本文 https://www.preethikasireddy.com/post/how-does-ethereum-work-anyway。)
以太坊是一個(gè)全球可訪問的狀態(tài)機(jī),只能通過共識進(jìn)行更新。作為一個(gè)不可變的狀態(tài)機(jī),我們可以向它寫入狀態(tài)(即數(shù)據(jù)),但是我們不能更新狀態(tài)。這意味著:
這有一個(gè)很好的理由:智能合約讓我們在參與者之間創(chuàng)建一個(gè)牢不可破的合約。但這也意味著我們永遠(yuǎn)受合同約束。因此,錯(cuò)誤修復(fù)和改進(jìn)是不可能的。
如果您來自 Web 2.0,您可能想知道為什么有人會開發(fā)無法更新的東西。甚至想想都覺得害怕。這就是為什么許多加密項(xiàng)目需要數(shù)月甚至數(shù)年才能將其應(yīng)用程序部署到區(qū)塊鏈上的原因——智能合約中的任何錯(cuò)誤或漏洞都可能造成數(shù)百萬美元的損失。
此類黑客的例子數(shù)不勝數(shù),您可以在此處:??https://medium.com/firmonetwork/3-famous-smart-contract-hacks-you-should-know-dffa6b934750?閱讀有關(guān)這些內(nèi)容的信息?https://consensys.github.io/smart-contract-best-practices/known_attacks/?……以及通過簡單的 Google 搜索即可找到更多內(nèi)容。
如果我們一定需要進(jìn)行更新怎么辦呢?
這就引出了我們的下一個(gè)問題:如果我們絕對需要進(jìn)行更新怎么辦?
假設(shè)開發(fā)人員犯了一個(gè)價(jià)值數(shù)百萬美元的錯(cuò)誤;有人破解了他們的智能合約并榨取了大量資金。當(dāng)然,必須采取措施阻止其他用戶使用這些智能合約——但這是一個(gè)非常乏味的過程。
以下是這種情況下通常會發(fā)生的情況:
第一步
您發(fā)現(xiàn)該漏洞的?通過暫停智能聯(lián)系人,您可以做兩件事:首先,明確表示用戶不應(yīng)使用它們,其次,防止攻擊者利用不知道該漏洞的用戶。
第二步
接下來,您需要恢復(fù)數(shù)據(jù),以便遷移到新的智能合約。請記住,在 Web 3.0 中,您的智能合約存儲應(yīng)用程序的邏輯和數(shù)據(jù)(有關(guān) Web 3.0 架構(gòu)和智能合約的入門,在隨后的文章中會進(jìn)行介紹)。當(dāng)您部署具有更新邏輯(修復(fù)您的漏洞)的新智能合約時(shí),您需要恢復(fù)該數(shù)據(jù);否則,一切都會被抹去。您將恢復(fù)的數(shù)據(jù)示例包括:
第三步
接下來,您使用恢復(fù)的數(shù)據(jù)編寫并啟動(dòng)新合同。如果您的數(shù)據(jù)很少,則可以在一次事務(wù)中完成此過程。但是,如果您有大量數(shù)據(jù),則必須將其分解為許多較小的事務(wù)。回想一下,將數(shù)據(jù)寫入以太坊區(qū)塊鏈需要花錢,而這筆錢是用“Gas”支付的。以太坊對每筆交易都有一個(gè)“GasLimit”;如果交易的 gas 成本超過此限制,礦工將不會將其包含在區(qū)塊中。
第四步
部署新合約意味著合約地址發(fā)生了變化。因此,您現(xiàn)在需要使用新地址更新與舊合約交互的所有合約。此外,您需要讓您的用戶知道這是他們應(yīng)該使用的新合同。這需要大量的社會協(xié)調(diào),并不總是那么簡單,但很有可能。
這種乏味的方法的好處是我們不會犧牲不變性,這是智能合約的基本屬性。但是,不可否認(rèn)遷移數(shù)據(jù)和說服用戶遷移的乏味。這就是為什么任何將智能合約部署到區(qū)塊鏈上的人都必須在部署之前制定遷移計(jì)劃的原因——這樣,如果他們發(fā)現(xiàn)了一個(gè)漏洞,他們就有了一個(gè)備份計(jì)劃。
構(gòu)建可升級的智能合約:一種后門方法
雖然我們剛剛討論的手動(dòng)方法有效,但它仍然不理想。有很多例子表明價(jià)值數(shù)百萬美元的以太幣被盜或被黑客入侵,如果我們能夠更新智能合約,這些以太幣本可以被挽救。擁有升級合約的能力不僅對迭代開發(fā)有用,而且對修復(fù)可能會消耗人們儲蓄的嚴(yán)重錯(cuò)誤也很有用。
這就是以太坊社區(qū)(特別是OpenZeppelin?https://openzeppelin.com/)提出“可升級智能合約”概念的原因。
這個(gè)概念很簡單。我們有一個(gè)“代理合約”和一個(gè)“邏輯合約”。代理合約委托到邏輯契約。您的最終用戶始終與您的代理合同進(jìn)行交互,該合同存儲您的所有應(yīng)用程序數(shù)據(jù)。但是,該方法的實(shí)際邏輯存儲在邏輯合約中。
當(dāng)用戶與代理合約交互時(shí),它會將這些交易轉(zhuǎn)發(fā)到邏輯合約并從函數(shù)調(diào)用中檢索返回?cái)?shù)據(jù)。然后將數(shù)據(jù)轉(zhuǎn)發(fā)回調(diào)用者。

因此,當(dāng)您需要更新邏輯時(shí),您只需更新邏輯合約并重新部署它。

請注意,這兩個(gè)智能合約仍然是不可變的。您只是換出代理合約調(diào)用的邏輯合約。由于用戶只與代理合約進(jìn)行交互,因此您無需精心制作歌曲和舞蹈來讓每個(gè)人切換到新合約。
代理合同如何在幕后運(yùn)作
代理合約只定義了一個(gè)方法:回退函數(shù)(https://solidity.readthedocs.io/en/v0.6.12/contracts.html#fallback-function)。如果沒有其他函數(shù)與給定的函數(shù)簽名匹配,則在調(diào)用合約時(shí)執(zhí)行此操作。因此,當(dāng)代理合約被調(diào)用時(shí),由于沒有定義其他方法,所以調(diào)用了回退函數(shù)。回退函數(shù)具有告訴合約將調(diào)用轉(zhuǎn)發(fā)到邏輯合約的邏輯。它可以做到這一點(diǎn),而無需特別了解邏輯合約的接口。
如果您不熟悉 Solidity,您可能想知道智能合約如何將函數(shù)調(diào)用轉(zhuǎn)發(fā)到另一個(gè)合約。Solidity 有一個(gè)叫做“?delegatecall?”的概念,它類似于合約調(diào)用,只是有一點(diǎn)點(diǎn)不同。
假設(shè)我們有執(zhí)行的合約 A委托調(diào)用合約 B,然后合約 B 的代碼與合約 A 的storage、msg.sender和msg.value 一起執(zhí)行。
我們將使用Solidity by Example網(wǎng)站中提供的示例來演示 delegatecall 的工作原理。

在這個(gè)例子中,合約 A 使用委托調(diào)用來調(diào)用合約 B 上的setVars,只是我們使用合約 A 的存儲而不是合約 B 的存儲中的數(shù)據(jù)來執(zhí)行調(diào)用。換句話說,存儲、當(dāng)前地址和余額仍然參考調(diào)用合約(即合約A),并且僅從被調(diào)用地址(即合約B)中取出代碼。
因此,在我們的可升級智能合約方案中,當(dāng)用戶調(diào)用代理合約時(shí),會調(diào)用回退函數(shù),然后使用代理合約中的數(shù)據(jù)“委托”對邏輯合約中定義的方法的調(diào)用。

有關(guān)詳細(xì)示例,我建議您閱讀OpenZeplin 關(guān)于可升級智能合約的文檔的這一頁(https://docs.openzeppelin.com/learn/upgrading-smart-contracts)。
請注意,用戶或惡意行為者仍然可以直接向邏輯合約發(fā)送交易。但是,這不會構(gòu)成威脅,因?yàn)檫壿嬈跫s狀態(tài)的更改不會影響您的應(yīng)用程序;您的應(yīng)用程序?qū)?shù)據(jù)存儲在代理合約而不是邏輯合約中。
可升級智能合約的優(yōu)缺點(diǎn)
可升級的智能合約已經(jīng)變得非常流行。然而,對于可升級合同是好事還是壞事,業(yè)內(nèi)存在分歧。Trail of Bits 在這篇文章中(https://blog.trailofbits.com/2018/09/05/contract-upgrade-anti-patterns/)做得很好,概述了使用 delegatecall 進(jìn)行可升級合約的風(fēng)險(xiǎn),我將在下面進(jìn)行總結(jié)。
假設(shè)用戶調(diào)用代理合約,然后調(diào)用邏輯合約。邏輯合約執(zhí)行函數(shù)并將數(shù)據(jù)寫回代理合約。到現(xiàn)在為止還挺好。
這就是它變得棘手的地方:?當(dāng)邏輯合約嘗試寫入代理合約時(shí),它會在代理狀態(tài)的范圍內(nèi)這樣做。
讓我們使用OpenZeppelin 文檔中(https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies)提供的示例來說明為什么這可能是危險(xiǎn)的。我們使用以下變量定義了代理和邏輯合約。

代理將邏輯合約的地址存儲在第一個(gè)存儲槽中。邏輯合約將“所有者”的地址存儲在其第一個(gè)槽中。兩個(gè)變量的大小均為 32 字節(jié)。如果邏輯合約被執(zhí)行并寫入“所有者”變量,它將在代理合約的上下文中執(zhí)行。這意味著它會嘗試寫入代理合約的第一個(gè)存儲槽。這將導(dǎo)致存儲沖突。

OpenZeppelin 的可升級智能合約克服這個(gè)問題的方法是隨機(jī)化代理合約存儲槽。

這確保了邏輯合約寫入代理合約上已被另一個(gè)變量使用的存儲槽的可能性可以忽略不計(jì)。
這種隨機(jī)化存儲槽的方法被稱為“非結(jié)構(gòu)化存儲”。雖然它有效,但它增加了智能合約的復(fù)雜性,因此為錯(cuò)誤和嚴(yán)重錯(cuò)誤留下了更多空間。它還要求開發(fā)人員了解以太坊 EVM 的內(nèi)部結(jié)構(gòu)以及存儲的工作原理,這對于想要構(gòu)建應(yīng)用程序的人來說是一種不公平的期望。
結(jié)論
如果您是 Web 3.0 的新手或正在考慮學(xué)習(xí) Web 3.0 開發(fā),那么了解智能合約的不可變性質(zhì)至關(guān)重要。這樣,您就可以規(guī)劃需要升級智能合約的場景。您是采用更乏味的傳統(tǒng)方法還是使用 OpenZeppelin 的可升級智能合約方案,取決于您的用例——以及您愿意做出的權(quán)衡。但是,無論哪種方式,都要制定計(jì)劃。
