摘要:接上篇合約升級(jí)模式介紹筆者改寫(xiě)了一個(gè)可用于實(shí)踐生產(chǎn)的升級(jí)框架,需要自取。在介紹合約升級(jí)模式中提到了一個(gè)可以解決這個(gè)問(wèn)題的方法。深度理解注意為中的低階方法下文中出現(xiàn)的方法,是我在智能合約中寫(xiě)的一個(gè)方法名稱(chēng),不要混淆。
接上篇:合約升級(jí)模式介紹智能合約升級(jí)的目的筆者改寫(xiě)了一個(gè)可用于實(shí)踐生產(chǎn)的升級(jí)框架,需要自取。https://github.com/hammewang/...
同時(shí)歡迎討論,微信xiuxiu1998
鑒于以太坊智能合約一旦部署,無(wú)法修改的原則,所以智能合約升級(jí)應(yīng)當(dāng)遵循如下兩點(diǎn)規(guī)則:
邏輯可升級(jí);
存儲(chǔ)可繼承;
第一點(diǎn)很好理解,可以把代理合約和邏輯合約看成插座和插頭的關(guān)系,需要升級(jí)的時(shí)候把老的插頭拔下,再插上新的即可。
對(duì)于第二點(diǎn),存儲(chǔ)可繼承,不僅僅是存儲(chǔ)結(jié)構(gòu)的繼承,而且在存儲(chǔ)內(nèi)容上,實(shí)現(xiàn)擴(kuò)展:舊存儲(chǔ)內(nèi)容不變,新存儲(chǔ)內(nèi)容繼續(xù)追加。這個(gè)過(guò)程類(lèi)似于城市化的推進(jìn),城市的邊緣可以一圈一圈擴(kuò)大,但是如果要尋址到老城區(qū)的XX路XX號(hào),無(wú)論城市怎么擴(kuò)大,拿著這個(gè)門(mén)牌號(hào)依然可以找到那棟老建筑。
升級(jí)方式升級(jí)目的中的第一點(diǎn)是相對(duì)好實(shí)現(xiàn)的,只要改變調(diào)用的邏輯合約地址就可以了;而為了實(shí)現(xiàn)第二點(diǎn),就要保證合約執(zhí)行環(huán)境上下文保持一致。在介紹合約升級(jí)模式中提到了一個(gè)可以解決這個(gè)問(wèn)題的方法:delegatecall。把關(guān)鍵代碼再貼一遍:
assembly { // 獲得自由內(nèi)存指針 let ptr := mload(0x40) // 復(fù)制calldata到內(nèi)存中 calldatacopy(ptr, 0, calldatasize) // 使用delegatecall處理calldata let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0) // 返回值大小 let size := returndatasize // 把返回值復(fù)制到內(nèi)存中 returndatacopy(ptr, 0, size) switch result case 0 { revert(ptr, size) } // 執(zhí)行失敗 default { return(ptr, size) } // 執(zhí)行成功,返回內(nèi)存中的返回值 }
這樣做,實(shí)現(xiàn)了把邏輯合約(_impl)中的方法拉到代理合約中執(zhí)行,遵循代理合約的上下文(如存儲(chǔ)、余額等),通過(guò)這種方式實(shí)現(xiàn)了執(zhí)行上下文一致性。
深度理解delegatecall注意:delegatecall為assembly中的低階方法;
下文中出現(xiàn)的delegateCall方法,是我在智能合約中寫(xiě)的一個(gè)方法名稱(chēng),不要混淆。
delegatecall的目的是可以維持執(zhí)行環(huán)境中上下文的一致性,一種很典型的應(yīng)用場(chǎng)景就是調(diào)用library中的方法,用的就是delegatecall。下面來(lái)具體介紹一下delegatecall的特點(diǎn)。
1. 可以傳遞msg.sender假設(shè)personA調(diào)用了contractA中的functionA,這個(gè)方法內(nèi)部同時(shí)使用了delegatecall調(diào)用了contractB中的functionB,那么對(duì)于functionB來(lái)說(shuō),msg.sender依然是personA,而不是contractA.
2.可以改變同一存儲(chǔ)槽中的內(nèi)容請(qǐng)看下面的合約:
pragma solidity ^0.4.24; contract proxy { address public logicAddress; function setLogic(address _a) public { logicAddress = _a; } function delegateCall(bytes data) public { this.call.value(msg.value)(data); } function () payable public { address _impl = logicAddress; require(_impl != address(0)); assembly { let ptr := mload(0x40) calldatacopy(ptr, 0, calldatasize) let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0) let size := returndatasize returndatacopy(ptr, 0, size) switch result case 0 { revert(ptr, size) } default { return(ptr, size) } } } function getPositionAt(uint n) public view returns (address) { assembly { let d := sload(n) mstore(0x80, d) return(0x80,32) } } } contract logic { address public a; function setStorage(address _a) public { a = _a; } }
這時(shí)分別部署proxy和logic,之后把logic.address賦值給proxy中的logicAddress變量。調(diào)用getPositionAt(0)會(huì)發(fā)現(xiàn)返回的也是logicAddress的值,結(jié)果如下圖:
這時(shí),如果調(diào)用proxy中的delegateCall并傳入0x9137c1a7000000000000000000000000bcb9c87f53878af6dd7a8baf1b24bab6a62fe7aa(9137c1a7是setStorage的方法簽名),意為用delegatecall調(diào)用logic中的setStorage方法,這時(shí)會(huì)發(fā)現(xiàn)proxy中的logicAddress發(fā)生了變化,變成了我們剛剛傳入的值。如下:
這時(shí)我們會(huì)發(fā)現(xiàn),delegatecall并不通過(guò)變量名稱(chēng)來(lái)修改變量值,而是修改變量所在的存儲(chǔ)槽。所以當(dāng)在proxy中delegatecallsetStorage方法時(shí),修改的并不是address a,而是address a所在的第0個(gè)存儲(chǔ)槽的值,而proxy中第0個(gè)存儲(chǔ)槽存放的是logicAddress,所以相應(yīng)就會(huì)被覆蓋。
理解到這一步,就可以感受到delegatecall的強(qiáng)大和危險(xiǎn)。但同時(shí)也帶來(lái)一層疑問(wèn):雖然使用delegatecall可以使用邏輯合約中的方法改變代理合約中相應(yīng)位置的變量,但是并沒(méi)有起到存儲(chǔ)可擴(kuò)展呀?不還得事先在代理合約中創(chuàng)建相應(yīng)變量么?這就相當(dāng)于在1949年新中國(guó)建立的時(shí)候,就要規(guī)劃以后建設(shè)的所有布局,包括共享單車(chē)??奎c(diǎn),這不是有點(diǎn)扯淡么?
這就要說(shuō)到delegatecall下面一個(gè)特點(diǎn)了。
delegatecall——"無(wú)中生有"delegatecall還有一個(gè)強(qiáng)大的特點(diǎn)就是,可以為proxy中未事先聲明的變量開(kāi)辟存儲(chǔ)空間。
我們來(lái)看下一個(gè)例子,代理合約依然使用上面用過(guò)的proxy,我們把邏輯合約 變一下:
contract logic2 { address public a; address public b; function setStorageB(address _a) public { b = _a; } }
新增加一個(gè)address變量,并且只修改第二個(gè)address變量。
這時(shí)依然重復(fù)上一個(gè)例子的第一步,把logic2的地址賦值給代理合約中的logicAddress變量。結(jié)果如下圖:
然后使用代理合約中的detegateCall方法,調(diào)用logic2中的setStorage2方法,傳入data為0x9ea338be0000000000000000000000000dcd2f752394c41875e259e00bb44fd505297caf。之后再調(diào)用getPositionAt(1)和logicAddress()方法,結(jié)果如下圖:
可以看到logicAddress并沒(méi)有發(fā)生變化,而第1個(gè)存儲(chǔ)槽中的值變成了我們剛剛傳入的值。
這也再次說(shuō)明了,delegatecall方法并不是按照變量名稱(chēng)操作的,而是按照變量所對(duì)應(yīng)的存儲(chǔ)槽的位置,對(duì)該位置中的值進(jìn)行操作。因此,我們是不是事先在代理合約中聲明了變量,就并不重要了。
delegatecall總結(jié)可以傳遞msg.sender
不按照變量名進(jìn)行操作,而是去找變量對(duì)應(yīng)的存儲(chǔ)槽進(jìn)行操作(無(wú)論變量是否在代理合約中事先聲明)
正因?yàn)榈诙c(diǎn)特性,為合約升級(jí)中的存儲(chǔ)擴(kuò)展提供了可能性;同時(shí),也提出了一個(gè)很?chē)?yán)格的要求:
新合約和舊合約之間必須嚴(yán)格遵守繼承的模式,即:
contract newLogic is previousVersionLogic{ ... }使用存儲(chǔ)繼承模式升級(jí) 原理介紹
------- ========================= | Proxy | ║ UpgradeabilityStorage ║ ------- ========================= ↑ ↑ ↑ --------------------- ------------- | UpgradeabilityProxy | | Upgradeable | --------------------- ------------- ↑ ↑ ---------- ---------- | Token_V0 | ← | Token_V1 | ---------- ----------
代理合約是UpgradeabilityProxy實(shí)例,圖中的Token_V0和Token_V1即是邏輯合約的最初版和升級(jí)版,它們都必須繼承Upgradeable,同時(shí)邏輯合約和代理合約都必須繼承UpgradeabilityStorage,繼承同一套存儲(chǔ)結(jié)構(gòu),以保證邏輯合約在代理合約中執(zhí)行時(shí),不會(huì)出現(xiàn)變量覆蓋的情況。
具體代碼結(jié)構(gòu)注:圖中每個(gè)方框的結(jié)構(gòu)從上到下依次是:合約名稱(chēng)、狀態(tài)變量、function、event、modifier
圖中可以更加清晰地看到,代理合約和邏輯合約都必須繼承registry和_implementation兩個(gè)狀態(tài)變量,并且邏輯合約中沒(méi)有修改前兩個(gè)狀態(tài)變量的相應(yīng)方法,因此代理合約中的存儲(chǔ)安全。
升級(jí)操作 1. 如何初始化部署Registry合約
部署邏輯合約的初始版本(V1),并確保它繼承了Upgradeable合約
向Registry合約中注冊(cè)這個(gè)最初版本(V1)的地址
要求Registry合約創(chuàng)建一個(gè)UpgradeabilityProxy實(shí)例
調(diào)用你的UpgrageabilityProxy實(shí)例來(lái)升級(jí)到你最初版本(V1)
2. 如何升級(jí)部署一個(gè)繼承了你最初版本合約的新版本(V2),V2必須繼承V1
向Registry中注冊(cè)合約的新版本V2
調(diào)用你的UpgradeabilityProxy實(shí)例來(lái)升級(jí)到最新注冊(cè)的版本
3. 如何轉(zhuǎn)移proxy合約所有權(quán)調(diào)用Registry中的transferProxyOwnership方法進(jìn)行所有權(quán)轉(zhuǎn)移;
代碼調(diào)用注意事項(xiàng)須對(duì)代理合約的地址套用當(dāng)前版本的邏輯合約的ABI,方能正常調(diào)用和獲取返回值。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/24162.html
摘要:以后的邏輯合約可以升級(jí)現(xiàn)有的方法或者創(chuàng)造新的方法,但是不能引入新的狀態(tài)變量。使用非結(jié)構(gòu)化存儲(chǔ)升級(jí)非結(jié)構(gòu)化存儲(chǔ)模式和繼承存儲(chǔ)類(lèi)似,但是不要求邏輯合約繼承任何和升級(jí)相關(guān)的狀態(tài)變量。 以太坊最大的優(yōu)勢(shì)就是,每一筆用來(lái)轉(zhuǎn)賬、部署合約或者和合約交互的交易(事務(wù))都被存在一個(gè)叫做區(qū)塊鏈的公共賬本上。一旦交易發(fā)生,就再也無(wú)法隱藏或者改變。這帶來(lái)一個(gè)巨大的好處,就是在以太坊中的每一個(gè)節(jié)點(diǎn)都可以去驗(yàn)證任...
摘要:引言給迷失在如何學(xué)習(xí)區(qū)塊鏈技術(shù)的同學(xué)一個(gè)指引,區(qū)塊鏈技術(shù)是隨比特幣誕生,因此要搞明白區(qū)塊鏈技術(shù),應(yīng)該先了解下比特幣。但區(qū)塊鏈技術(shù)不單應(yīng)用于比特幣,還有非常多的現(xiàn)實(shí)應(yīng)用場(chǎng)景,想做區(qū)塊鏈應(yīng)用開(kāi)發(fā),可進(jìn)一步閱讀以太坊系列。 本文始發(fā)于深入淺出區(qū)塊鏈社區(qū), 原文:區(qū)塊鏈技術(shù)學(xué)習(xí)指引 原文已更新,請(qǐng)讀者前往原文閱讀 本章的文章越來(lái)越多,本文是一個(gè)索引帖,方便找到自己感興趣的文章,你也可以使用左側(cè)...
摘要:以太坊開(kāi)發(fā)高級(jí)語(yǔ)言學(xué)習(xí)。地址以太坊區(qū)塊鏈由賬戶(hù)組成,你可以把它想象成銀行賬戶(hù)。使用很安全,因?yàn)樗哂幸蕴粎^(qū)塊鏈的安全保障除非竊取與以太坊地址相關(guān)聯(lián)的私鑰,否則是沒(méi)有辦法修改其他人的數(shù)據(jù)的。 以太坊開(kāi)發(fā)高級(jí)語(yǔ)言學(xué)習(xí)。 一、映射(Mapping)和地址(Address) 我們通過(guò)給數(shù)據(jù)庫(kù)中的僵尸指定主人, 來(lái)支持多玩家模式。 如此一來(lái),我們需要引入2個(gè)新的數(shù)據(jù)類(lèi)型:mapping(映射)...
摘要:狀態(tài)變量合約內(nèi)聲明的公有變量還有一個(gè)存儲(chǔ)位置是,用來(lái)存儲(chǔ)函數(shù)參數(shù),是只讀的,不會(huì)永久存儲(chǔ)的一個(gè)數(shù)據(jù)位置。稱(chēng)這個(gè)為狀態(tài)改變,這也是合約級(jí)變量稱(chēng)為狀態(tài)變量的原因。 本文首發(fā)于深入淺出區(qū)塊鏈社區(qū)原文鏈接:智能合約語(yǔ)言 Solidity 教程系列4 - 數(shù)據(jù)存儲(chǔ)位置分析原文已更新,請(qǐng)讀者前往原文閱讀 Solidity教程系列第4篇 - Solidity數(shù)據(jù)位置分析。 寫(xiě)在前面 Solidity...
摘要:另外只能做狀態(tài)變量,不能做本地局部變量。語(yǔ)法聲明映射類(lèi)型包括類(lèi)型包括狀態(tài)變量報(bào)錯(cuò)??梢暥戎傅氖牵瑳Q定函數(shù)或者狀態(tài)變量的可以被哪些智能合約可見(jiàn)和調(diào)用。狀態(tài)變量可見(jiàn)性沒(méi)有。在中,通過(guò)來(lái)抽象出狀態(tài)變量自增的代碼,并修飾。 Mapping 映射(Mappings):類(lèi)似于哈希表。mapping 中任何一個(gè)可能的 key 都對(duì)應(yīng)著一個(gè) value,它的默認(rèn)值是default-value 。底層用...
閱讀 3907·2021-11-22 13:54
閱讀 2680·2021-09-30 09:48
閱讀 2363·2021-09-28 09:36
閱讀 3117·2021-09-22 15:26
閱讀 1346·2019-08-30 15:55
閱讀 2513·2019-08-30 15:54
閱讀 1427·2019-08-30 14:17
閱讀 2345·2019-08-28 18:25