Solidity(或者更概略的說(shuō),EVM),在軟件工程師的生產(chǎn)效率和語(yǔ)言表達(dá)能力都還有好長(zhǎng)一段路需要走。如果你曾經(jīng)在以太坊上開(kāi)發(fā)智能合約,到現(xiàn)在你應(yīng)該已經(jīng)察覺(jué),Solidity真是個(gè)綁手綁腳的語(yǔ)言。
特別是在你是從Swift或Javascript轉(zhuǎn)行過(guò)來(lái)開(kāi)發(fā)Solidity。用Solidity開(kāi)發(fā)程序,這個(gè)語(yǔ)言允許軟件工程師能做的事情,還有整個(gè)語(yǔ)言的表達(dá)能力,都讓人有種倒退走的感覺(jué)。
這種感覺(jué)有時(shí)候真的會(huì)讓人發(fā)飆。
但為什么它是受限的語(yǔ)言
Solidity和其他能夠被編譯成能夠在EVM(以太坊虛擬機(jī))上運(yùn)作的bytecode(位組碼),都會(huì)受到某些限制,因?yàn)椋?/p>
· 當(dāng)你要執(zhí)行你的程序的時(shí)候,你的程序會(huì)在整個(gè)系統(tǒng)上的每一臺(tái)節(jié)點(diǎn)上運(yùn)作。當(dāng)一個(gè)節(jié)點(diǎn)收到新的區(qū)塊的時(shí)候,這個(gè)節(jié)點(diǎn)就會(huì)驗(yàn)證這個(gè)區(qū)塊的完整性。在以太坊上這些驗(yàn)證包含了,這個(gè)區(qū)塊所包含的所有計(jì)算是正確的,而且合約的每個(gè)狀態(tài)都是計(jì)算正確的。
· 這造成了,即便EVM是圖靈完備,大量的計(jì)算會(huì)十分昂貴(甚至?xí)驗(yàn)槌^(guò)燃料(gas)上限而直接不被允許),因?yàn)槊總€(gè)節(jié)點(diǎn)都需要把這些計(jì)算計(jì)算一遍。也就因此把整個(gè)以太網(wǎng)絡(luò)拖慢。
· 標(biāo)準(zhǔn)函式庫(kù)(standard library)還沒(méi)被開(kāi)發(fā)完成。尤其是數(shù)組和字串都很難用。我本人都還曾經(jīng)自己實(shí)作過(guò)操作字串的函式庫(kù),就為了一些很基本,我們之前都習(xí)以為常的功能。
· 你所寫(xiě)的合約沒(méi)辦法從外界(EVM之外)獲得任何數(shù)據(jù),除非透過(guò)交易(Oracle)。而且當(dāng)一筆交易被布署到網(wǎng)絡(luò)上,他就沒(méi)把辦法再升級(jí)改版(除非是透過(guò)migration或是純儲(chǔ)存合約(pure storage contract))。
這些限制有些是以太坊必須一定存在的限制(就像是你永遠(yuǎn)不可能會(huì)有辦法儲(chǔ)存你Google Photos上照片的備份在鏈上,或者單純透過(guò)鏈上的計(jì)算資源去做圖像辨識(shí),但這也沒(méi)什么關(guān)系啦)。但其他限制會(huì)存在,只是因?yàn)檫@是一個(gè)十分新的科技(雖然真的進(jìn)步的超級(jí)快),但這個(gè)技術(shù)也將會(huì)一直改善。
好的所以我到底要怎么解決這個(gè)問(wèn)題?
當(dāng)我們?cè)陂_(kāi)發(fā)項(xiàng)目的時(shí)候,我們可能在未來(lái)會(huì)對(duì)合約做更改。我們可以透過(guò)橫跨不同合約間信息傳遞的方式去間接解決這個(gè)問(wèn)題。在進(jìn)入可升級(jí)智能合約(Upgradable Smart Contracts)的實(shí)作之前,讓我們先來(lái)了解它的限制。
在Solidity,函式庫(kù)(library)是一種特殊的合約,這種合約不會(huì)儲(chǔ)存任何數(shù)據(jù)(storage),并且也不能持有任何以太幣。有時(shí)候我們可以試著把函式庫(kù)當(dāng)成一種以太坊虛擬機(jī)(EVM)的單例(Singleton)就好。這個(gè)單例是一段可以被其他合約呼叫的代碼,而且這段代碼在被呼叫的時(shí)候不需要重新布署。這個(gè)特性解決了一些我們所面對(duì)的大問(wèn)題,比如說(shuō):
· 布署所需的燃料(gas)花費(fèi):因?yàn)橥瑯拥拇a不需要一而再再而三地被布署,所以一個(gè)很明顯的優(yōu)勢(shì)就是能夠節(jié)省大量的燃料。并且不同的合約可以都倚賴同一個(gè)已經(jīng)被布署出去的函式庫(kù)。
· 繁冗的代碼在區(qū)塊鏈重復(fù)出現(xiàn):這明顯的是上面那點(diǎn)所伴隨來(lái)的好處,布署的次數(shù)比較少,區(qū)塊鏈上的紀(jì)錄也就比較少。
· 代碼升級(jí):在之前的狀況是,如果需要修改程序錯(cuò)誤或者幫合約改版,就需要重新布署一個(gè)新的,因而與之前合約獨(dú)立的合約(甚至更慘的狀況是像之前一樣要對(duì)以太方進(jìn)行硬分岔)。這個(gè)問(wèn)題因此被解決了
有沒(méi)有開(kāi)始覺(jué)得函式庫(kù)是一個(gè)很厲害的東西了阿?不幸的是,函式庫(kù)也有一些限制,下面是幾點(diǎn)是關(guān)于函式庫(kù)我們必須知道的重要信息:
· 沒(méi)有儲(chǔ)存(storage)的能力
· 函式庫(kù)能夠操縱其他合約的儲(chǔ)存(storage)
· 函式庫(kù)不能有任何payable的函式(function)。
· 函式庫(kù)不能有任何fallback的函式(function)。
· 函式庫(kù)不能有事件日志(event log)。
· Libraries can be used to fire event logs for the contract which uses it.
· 函式庫(kù)是不能被繼承的
· 雖然函式庫(kù)不能直接被繼承,但是函式庫(kù)可以跟其他函式庫(kù)接在一起,就能像一個(gè)一般合約一樣的使用被接上的函式庫(kù),單這樣使用,函式庫(kù)本身的限制依然還會(huì)在。
這幾點(diǎn)可能在一開(kāi)始會(huì)讓人聽(tīng)起來(lái)很混亂,但是別灰心,這邊有一個(gè)非常棒的資源可以協(xié)助你了解函式庫(kù)。
但至少接下來(lái),我們只會(huì)用上一些,因?yàn)榱私?、?shí)作可升級(jí)智能合約所必須了解的部分就好了。
函式庫(kù)是如何運(yùn)作的?
函式庫(kù)是一種特殊的合約,這種合約不能有任何payable函式,而且也不能有任何fallback函式(這些限制在編譯期間就被強(qiáng)行限制了,因此可以讓函式庫(kù)這種合約絕無(wú)可能持有任何資金)。函式庫(kù)是透過(guò)函式庫(kù)關(guān)鍵字(library L{})去定義的,就像一個(gè)合約會(huì)透過(guò)(contract C{})去定義一樣。
library L{
function a() returns (address) {
return address(this);
}
}
contract C{
function a() constant returns (address) {
//This will behave as if the library code was written within this contract
return L.a();
}
}
要呼叫一個(gè)函式庫(kù)里面的函式必須用特別的指令(DELEGATECALL),這個(gè)指令會(huì)傳遞呼叫函式的信息(calling context)到函式庫(kù),因此就幾乎像是一個(gè)都在同個(gè)合約里面,合約自己處理自己的指令。我真的蠻喜歡在Solidity文件里面闡述這件事情的角度。
函式庫(kù)可以被視為給其他智能合約使用的固有基礎(chǔ)合約
從上面的的代碼可以知道,如果合約C的函式a()被呼叫,a()會(huì)回傳合約C的地址而非函式庫(kù)L的地址。同樣這點(diǎn)也適用所有msg的性質(zhì):msg.sender,msg.value,msg.sig,msg.data以及msg.gas(Solidity文件里面所寫(xiě)的與這相反,但是在做了一些實(shí)驗(yàn)之后,似乎我這樣解釋才是正確的)。
一件我們?cè)谶@邊會(huì)注意到的事情是,我們還是不太清楚類型C(class C)和函式庫(kù)L(library L)是怎么被連在一起的,因此接下來(lái)我們就會(huì)試著了解這件事情。
函式庫(kù)們是如何被連接起來(lái)的?
與顯式繼承不同的是,一個(gè)倚賴函式庫(kù)合約(contract C is B {}),他與函式庫(kù)之間的連接(link)方式是沒(méi)有那么清楚的。在上述例子,合約C在他自己的函式a()中用到函式庫(kù)L。但是在上面這短短的代碼中,我們都沒(méi)有提到函式庫(kù)要使用什么地址,而且函式庫(kù)L不會(huì)出現(xiàn)在合約C編譯完的位組碼(byecode)中。
函式庫(kù)的連接是發(fā)生在位組碼等級(jí)。當(dāng)合約C被編譯完成后,合約C會(huì)替函式庫(kù)的地址給一個(gè)像是下面這種形式的預(yù)留位置0073__L_____________________________________630dbe671f(0dbe671f是a()的function signature),如果我們完全不更動(dòng)合約C就布署合約C,這樣就會(huì)因?yàn)槲唤M碼是不合法的導(dǎo)致布署失敗。
簡(jiǎn)單解釋就是,函式庫(kù)連接就是簡(jiǎn)單的把所有在合約位組碼里面預(yù)留給函式庫(kù)的位置,全部都用函式庫(kù)的地址填上就完成了。這樣填完的位組碼就可以順利的布署到區(qū)塊鏈上。
好的我們現(xiàn)在介紹了函式庫(kù)的基本概念后,我們就可以了解怎么用函式庫(kù)去開(kāi)發(fā)可升級(jí)智能合約了。
函式庫(kù)本身是沒(méi)辦法升級(jí)的
對(duì)的函式庫(kù)沒(méi)辦法升級(jí),而且合約也沒(méi)辦法升級(jí)。就像我們本篇文章前面所提到的,對(duì)函式庫(kù)的參考(reference)是位組碼(bytecode)等級(jí)的事情,而不是儲(chǔ)存(storage)等級(jí)的事情。在合約一布署后,就無(wú)法修改合約的位組碼。因此對(duì)于函式庫(kù)的參考就會(huì)被隨著合約永存。
所以我們必須問(wèn)這個(gè)問(wèn)題「為什么還是有人提出可升級(jí)這個(gè)特點(diǎn)呢?」
最后,他到底怎么運(yùn)作的?
這邊我們會(huì)用到一些小技巧,讓我們一起仔細(xì)了解。
我們沒(méi)有直接將給使用者使用的主合約C和布署所需的函式庫(kù)連接,而是將給使用者使用的主合約C跟一個(gè)調(diào)度員(Dispatcher)合約連接起來(lái)。在編譯時(shí)期和布署時(shí)期,因?yàn)檎{(diào)度員合約沒(méi)有實(shí)作函式庫(kù)內(nèi)任何函數(shù),所以一切都不會(huì)出現(xiàn)問(wèn)題。這也就意味著,調(diào)度員合約并沒(méi)有用到任何函式庫(kù)內(nèi)的代碼,調(diào)度員合約僅僅是位組碼(就像是我們?cè)谏厦婵吹胶霞sC的位組碼一樣),在調(diào)度員的位組碼中,并不需要引用(include)函式庫(kù)的地址。我們就沒(méi)有將任何地址寫(xiě)死(hardcode)在位元組碼等級(jí),也因此我們就可以隨時(shí)把函式庫(kù)替換成任何我們所需的函式庫(kù)。
然而,如果我們沒(méi)有在調(diào)度員合約中用任何函式庫(kù)的代碼,這樣我們要如何執(zhí)行函式庫(kù)中的函式?
當(dāng)一筆交易進(jìn)來(lái)的時(shí)候,主合約(Token contract,代幣合約)發(fā)現(xiàn)這筆交易需要從主合約所連接的函式庫(kù)(TokenLib1)呼叫delegatecall,然而呼叫delegatecall這件事情不會(huì)直接請(qǐng)函式庫(kù)直接回傳,而是會(huì)先透過(guò)調(diào)度員合約呼叫delegatecall。
接下來(lái)的事情就會(huì)變得很有趣了。一當(dāng)調(diào)度員合約在他自己的fallback function中接收到delegatecall,調(diào)度員合約會(huì)在fallback function中判斷所需的正確版本函式庫(kù)是哪個(gè),然后將呼叫delegatecall的要求導(dǎo)向正確的函式庫(kù)。當(dāng)函式庫(kù)回傳數(shù)據(jù)后,數(shù)據(jù)就會(huì)一路回到主合約。
雖然這個(gè)解法運(yùn)作起來(lái)還不錯(cuò),但是他還是有些限制。
限制
調(diào)度員合約必須知道呼叫函式庫(kù),函式庫(kù)回傳值的內(nèi)存大小?,F(xiàn)在這個(gè)問(wèn)題可以透過(guò)mapping解決,會(huì)以function signature去取得回傳值大小。為了簡(jiǎn)化說(shuō)明,所以我們故意避免對(duì)這方面多做解釋。
上述方法在以太坊虛擬機(jī)中可以成功運(yùn)行,但是我們只能在不同合約間儲(chǔ)存足跡(storage footprint)都相同的狀況下才能使用。因?yàn)楹綆?kù)沒(méi)有任何儲(chǔ)存,所以我們必須讓調(diào)度員合約里面也沒(méi)有任何儲(chǔ)存。這也就是為什么需要有另外一個(gè)分離的Dispatcher Storage(見(jiàn)上圖下面方塊)去儲(chǔ)存調(diào)度員合約所需的數(shù)據(jù)。還有,Dispatcher Storage的地址必須寫(xiě)死在調(diào)度員合約的位組碼里面。
可以發(fā)現(xiàn),使用者所會(huì)面使用到的合約(Token contract)其實(shí)沒(méi)有使用到任何特別的技術(shù),唯一不一樣的地方就是,不能像之前一樣連接到某特定版本的函式庫(kù),而是連接到調(diào)度員合約。
評(píng)論