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