国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

智能合約升級模式介紹 — 入門篇

YFan / 1404人閱讀

摘要:以后的邏輯合約可以升級現有的方法或者創造新的方法,但是不能引入新的狀態變量。使用非結構化存儲升級非結構化存儲模式和繼承存儲類似,但是不要求邏輯合約繼承任何和升級相關的狀態變量。

以太坊最大的優勢就是,每一筆用來轉賬、部署合約或者和合約交互的交易(事務)都被存在一個叫做區塊鏈的公共賬本上。一旦交易發生,就再也無法隱藏或者改變。這帶來一個巨大的好處,就是在以太坊中的每一個節點都可以去驗證任意一筆交易的合法性和當前狀態。這使得以太坊成為一個非常健壯的去中心化系統。

但是隨之而來的是,它還有一個最大的缺點,就是智能合約一旦部署之后,就再也無法改變源碼。開發中心化應用(比如facebook或者Airbnb)的開發者,都已經習慣了,為了修復bug或者引入新的特性而頻繁更新產品。但這種方式卻不適用以太坊。

還記得當面Parity多簽名錢包被黑導致150000以太幣被偷的惡劣事件嗎?在整個攻擊中,就因為錢包中的一個bug導致很多巨額錢包的資金被清空。而唯一的解決方案就是嘗試以比黑客更快的速度,利用相同的漏洞攻擊剩余的錢包,來把以太幣重新分配給它們合法的所有者。

要是有一種方法可以在智能合約部署之后,還能對它們進行升級,那該多好...

引入代理模式

盡管想升級已經部署的智能合約中的代碼是不可能的,但是可以通過設計一個代理合約結構,這個結構可以讓你可以通過新部署一個合約的方式,來實現升級主要的處理邏輯的目的。

代理結構模式就像下面這張圖一樣:所有消息通過一個代理合約來間接調用最新部署的邏輯合約。如果想要升級的話,只需要部署一個新的合約,然后在代理合約中更新引用新的合約地址就可以了。

作為實現zeppelin_os的一部分,zeppelin正致力于實現集中代理模式。目前已經探索出來的有下面三個:

繼承存儲模式 Inherited Storage

永久存儲模式 Eternal Storage

非結構化存儲模式 Unstructured Storage

所有三種模式都依賴低階的delegatecall。盡管solidity提供了一個delegatecall方法,但它只能返回true或者false來顯示調用是否成功,而不是允許你操作返回的數據。

在我們深入了解之前,理解兩個關鍵的概念很重要:

當調用一個合約中并不支持的的方法時,就會調用合約中的fallback方法。你可以自己寫一個fallback函數來處理這種場景。代理合約就是用自定義的fallback方法將調用重定向到其他合約實現。

每當合約A授權對另一個合約B的調用時,它就會在合約A的上下文中執行合約B的代碼。這就意味著msg.valuemsg.sender的值會被保留。并且對存儲的修改將會作用在合約A的存儲上。

zeppelin的代理合約,為了可以返回調用邏輯合約后的結果,實現了自己的delegatecall方法,所有模式都是這樣。如果你想要使用zeppelin的代理合約代碼,你就要理解代碼的每一個細節。讓我們先來看看它是如何發揮作用的,以及理解為了達到目的它所使用的assembly操作碼。

 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) }
 }

為了授權對另一個合約中方法的調用,我們需要把它賦值給proxy合約接收的msg.data。因為msg.databytes類型的,是一個動態的數據結構,所以它在msg.data的第一個字(word,也就是32個字節)中的存儲長度會不一樣。如果我們想要只取出真正的數據,我們需要跳過第一個字(word),從msg.data0x20(32個字節)開始。然而,我們會用到兩個操作碼來實現此目的。我們會使用calldatasize來獲取msg.data的大小,以及calldatacopy來把它復制到ptr所指向的位置。

注意到我們是如何初始化ptr變量的。在solidity中,內存槽中的0x40位置是和特殊的,因為它存儲了指向下一個可用自由內存的指針。每次當你想往內存里存儲一個變量時,你都要檢查存儲在0x40的值。這就是你變量即將存放的位置。現在我們知道了我們要在哪兒存變量,我們就可以使用calldatacopy,把大小為calldatasize的calldata從0開始啊復制到ptr指向的那個位置了。

let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize)

我們再看看下面的assembly代碼(使用delegatecall操作碼)

let result := delegatecall(gas, _impl,  ptr, calldatasize, 0, 0)

解釋一下上面的參數:

gas:函數執行所需的gas

_impl:我們調用的邏輯合約的地址

ptr:內存指針(指向數據開始存儲的地方)

calldatasize:傳入的數據大小

0:調用邏輯合約后的返回值。我們沒有使用這個參數因為我們還不知道返回值的大小,所以不能把它賦值給一個變量。我們可以后面可以進一步使用returndata操作碼來獲取這些信息。

0:返回值的大小。這個參數也沒有被使用因為我們沒有機會創造一個臨時變量用來存儲返回值。鑒于我們在調用其他合約之前無法知道它的大小(所以就無法創造臨時變量呀)。我們稍后可以用returndatasize操作碼來得到這個值。

下面一行代碼就是用returndatasize操作碼得到了返回數據的大小:

let size := returndatasize

我們使用這個返回值的大小,來把返回值復制到ptr指向的內存,使用returndatacopy來達到這個目的:

returndatacopy(ptr, 0, size)

最后,switch語句要么返回 【返回值】,要么拋出錯誤,如果發生錯誤的話。

很好,我們現在有了一個從邏輯合約中獲取正確結果的方法。

現在,我們理解了代理合約是如何工作的。下面就讓我們正式學習三種模式:繼承存儲模式、永久存儲模式和非結構化存儲模式。

這三種方法用不同的方法來解決同一個難點:怎樣確保邏輯合約不會重寫/覆蓋代理中的狀態變量。

任何代理結構模式的主要問題就是如何分配存儲。記住,既然我們使用一個合約來存儲,另一個合約來實現邏輯,它們中的任何一個都有可能重寫一個已經使用的存儲槽。這意味著如果代理合約有一個狀態變量在某個存儲槽中存儲著最新的邏輯合約地址,但是邏輯合約卻不知道的話,那么邏輯合約可能就會在那個槽中存一些其他數據,這樣就把代理合約中的重要信息覆蓋了。zeppelin的這三種方法代表了架構合約系統的三種途徑,實現通過代理模式升級合約的目的。

使用繼承存儲模式升級

繼承存儲方法要求邏輯合約內部也實現代理合約內的存儲結構。代理合約和邏輯合約都要繼承完全一樣的存儲結構,來確保二者都支持存儲必要的代理合約的狀態變量。

當探索這種模式時,我們有這樣一個想法,我們想要有一個Registry合約來追蹤不同版本的邏輯合約。 為了升級成新的邏輯合約,你需要為它在Registry里注冊一個新的版本,并且要求代理合約中也升級成這個最新版本的邏輯合約。注意到有一個Registry合約并不影響存儲機制,實際上,它可以應用到這篇文章中提到的任意一種存儲模式中。

如何初始化

部署Registry合約

部署一個邏輯合約的最初版本(V1),確保它繼承了Upgradeable合約

Registry合約中注冊這個最初版本(V1)的地址

要求Registry合約創建一個UpgradeabilityProxy實例

調用你的UpgrageabilityProxy實例來升級到你最初版本(V1)

如何升級

部署一個繼承了你最初版本合約的新版本(V2),==確保它保留了代理合約和最初版本邏輯合約中的存儲結構==

Registry中注冊合約的新版本

調用你的UpgradeabilityProxy實例來升級到最新注冊的版本

tips

我們可以在未來部署的邏輯合約中升級現有方法、創造新的方法以及新的狀態變量,但仍然調用同一個UpgradeabilityProxy合約。

使用永久存儲模式升級

在永久存儲模式中,存儲模式用一個獨立的合約(代理和邏輯合約都要繼承這個合約)來定義。這個存儲合約保留了所有邏輯合約需要的狀態變量,因為代理合約也會知道這些變量的存在(因為繼承),它就可以為升級定義自己的狀態變量,不用考慮覆蓋變量這些問題。注意到所有的版本的邏輯合約都不可以再定義任何額外的狀態變量。所有版本的邏輯合約都必須一直使用一開始就定義好的永久存儲架構。

這種應用在zeppelin labs項目中提供了實現,并且同時引入了代理所有權的概念。一個代理的所有者是唯一一個可以升級代理并指定一個新的邏輯合約的地址,也是唯一一個可以轉移所有權的地址。

如何初始化

部署一個EternalStorageProxy實例

部署一個邏輯合約的最初版本(V1)

調用EternalStorageProxy實例來升級到這個最初版本合約的地址

如果你的邏輯合約依賴自己的構造函數(constructor)來設置某個初始狀態,那么在它和代理合約產生聯系之后,之前的這些狀態就要重新修改,因為代理合約的存儲并不知道(邏輯合約里的)這些值。EternalStorageProxy有一個叫upgradeToAndCall的函數專門來調用一些邏輯合約中的方法,一旦代理合約升級到最新版本時,就把連接到的那個邏輯合約里的初始設置重新設置一遍。

如何升級

部署一個邏輯合約的最新版本(v2),確保它也包含永久存儲結構。

調用EternalStorageProxy實例來升級到最新版本。

tips

這是沒有增加太多開銷同時很直觀的邏輯合約。 以后的邏輯合約可以升級現有的方法或者創造新的方法,但是不能引入新的狀態變量。

使用非結構化存儲升級

非結構化存儲模式和繼承存儲類似,但是不要求邏輯合約繼承任何和升級相關的狀態變量。這個模式使用代理合約中定義的非結構化的存儲槽來保存升級所需的數據。

在代理合約中,我們定義了一個常量,每當哈希的時候,就給出一個足夠隨機的存儲位置來存儲代理合約需要調用的邏輯合約的地址。

bytes32 private constant implementationPosition = 
                     keccak256("org.zeppelinos.proxy.implementation");

因為常量(恒定)狀態變量并不占用存儲槽,所以并不用擔心implementationPosition會不小心被邏輯合約占用。鑒于solidity在存儲中放置狀態變量的方法,依然有非常非常非常小的概率可能發生要存儲新變量的存儲槽已經被占用了。

通過使用這種模式,任何版本的邏輯合約都不需要知道代理合約的存儲結構,但是所有后一個版本的邏輯合約都必須繼承上一個版本的存儲變量。就像在繼承存儲模式中一樣,未來的邏輯合約可以更新現有的方法,也可以創建新的方法和新的狀態變量。

這個模式也使用了代理合約所有權的概念。只有代理合約的所有者可以更新邏輯合約的地址,也是唯一可以轉移所有權的地址。

如何初始化

部署OwnedUpgradeabilityProxy實例

部署邏輯合約的初始版本(V1)

調用OwnedUpgradeabilityProxy實例來更新到初始版本的邏輯合約

如果你的邏輯合約依賴自己的構造函數(constructor)來設置某個初始狀態,那么在它和代理合約產生聯系之后,之前的這些狀態就要重新修改,因為代理合約的存儲并不知道(邏輯合約里的)這些值。OwnedUpgradeabilityProxy有一個upgradeToAndCall方法專門來調用一些邏輯合約中的方法,一旦代理合約升級到最新版本時,就把連接到的那個邏輯合約里的初始設置重新設置一遍。

如何升級

部署一個新版本的邏輯合約(V2),確保它繼承了上一個版本里的狀態變量結構。

調用ownedUpgradeabilityProxy實例來升級到新版本合約的地址。

tips

這個方法很棒,因為它不需要邏輯合約知道它是整個代理系統的一部分。

關于升級

重要:如果你的邏輯合約依賴自己的構造器來設置一些初始狀態的話,這個過程在新版本的邏輯合約注冊到代理中時需要重新做一遍。舉個例子,邏輯合約繼承Zeppelin中的Ownable合約,這很常見。當你的邏輯合約繼承Ownable,它也就繼承了Ownable的構造器,構造器會在合約創建的時候就設置合約的所有者是誰。當你讓代理合約來使用你的邏輯合約的時候,代理合約是不知道邏輯合約的所有者是誰的。

升級代理合約的一種常見的模式就代理立即對邏輯合約調用一個初始化方法。這個初始化方法應該去模仿在構造器中做的一些事情。同時你也想要一個標識,用來確保你不可以再次對同一個邏輯合約調用初始化方法。(只能調用一次)

你的邏輯合約看上去可能像下面這樣:

contract Token is Ownable {
   ...
   bool internal _initialized;
   
   function initialize(address owner) public {
      require(!_initialized);
      setOwner(owner);
      _initialized = true;
   }
   ...
}

當然這取決于你的部署策略,你可以寫一個幫助部署的合約,或者你可以可以多帶帶部署代理合約和邏輯合約。如果你多帶帶部署的話,你需要使用upgradeToAndCall把代理合約鏈接到邏輯合約上,這看上去就會像下面這樣:

const initializeData = encodeCall("initialize", ["address"], [tokenOwner])
await proxy.upgradeToAndCall(logicContract.address, initializeData, { from: proxyOwner })
結論

代理模式的概念已經出來有一段時間了,但是由于太復雜了、害怕引入安全漏洞以及繞過了區塊鏈不可變的特性,它還沒有被廣泛接受。過去的解決方法在關于未來版本的邏輯合約可以添加和修改的東西上有嚴格的限制,這很不靈活。但是很顯然,開發者對于可升級合約的需求很迫切。zeppelin提供并且測試了三種模式,他們致力于幫助開發者架構自己的項目,引入可升級特性。

盡管代理模式的概念出來也有段時間了,但是它的應用依然處在非常早期。很開心看到越來越多的高級DApp架構通過這種方式得以實現。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/24163.html

相關文章

  • 區塊鏈技術學習指引

    摘要:引言給迷失在如何學習區塊鏈技術的同學一個指引,區塊鏈技術是隨比特幣誕生,因此要搞明白區塊鏈技術,應該先了解下比特幣。但區塊鏈技術不單應用于比特幣,還有非常多的現實應用場景,想做區塊鏈應用開發,可進一步閱讀以太坊系列。 本文始發于深入淺出區塊鏈社區, 原文:區塊鏈技術學習指引 原文已更新,請讀者前往原文閱讀 本章的文章越來越多,本文是一個索引帖,方便找到自己感興趣的文章,你也可以使用左側...

    Cristic 評論0 收藏0
  • SegmentFault 技術周刊 Vol.41 - 深入學習區塊鏈

    摘要:和比特幣協議有所不同的是,以太坊的設計十分靈活,極具適應性。超級賬本區塊鏈的商業應用超級賬本超級賬本是基金會下的眾多項目中的一個。證書頒發機構負責簽發撤 showImg(https://segmentfault.com/img/bV2ge9?w=900&h=385); 從比特幣開始 一個故事告訴你比特幣的原理及運作機制 這篇文章的定位會比較科普,盡量用類比的方法將比特幣的基本原理講出來...

    qianfeng 評論0 收藏0
  • 智能合約開發環境搭建及Hello World合約

    摘要:今天我們來一步一步從搭建以太坊智能合約開發環境開始,講解智能合約的如何編寫。開發環境搭建安裝強烈建議新手使用來進行開發。第行修改部署賬戶為新賬戶索引,即使用新賬戶來部署合約。 本文首發于深入淺出區塊鏈社區原文鏈接:智能合約開發環境搭建及Hello World合約原文已更新,請讀者前往原文閱讀 如果你對于以太坊智能合約開發還沒有概念(本文會假設你已經知道這些概念),建議先閱讀入門篇。就先...

    Winer 評論0 收藏0
  • 智能合約實施指南

    摘要:在協議結束時,智能合約被視為已履行并仍存儲在區塊鏈網絡中。這組條件和事件代表了最基本的一次性智能合約。智能合約用例智能合約越來越受歡迎,并已在各種區塊鏈項目中實施。 與區塊鏈技術一樣,智能合約在商業領域也非常有價值。 為了讓我們的讀者徹底了解智能合約是什么以及它們如何影響現代商業的交易方式,我們準備了本指南。 集中商業模式正在給去中心化的模式讓路 傳統的商業關系模型都是集中式的,始終存...

    meteor199 評論0 收藏0
  • 智能合約實施指南

    摘要:在協議結束時,智能合約被視為已履行并仍存儲在區塊鏈網絡中。這組條件和事件代表了最基本的一次性智能合約。智能合約用例智能合約越來越受歡迎,并已在各種區塊鏈項目中實施。 與區塊鏈技術一樣,智能合約在商業領域也非常有價值。 為了讓我們的讀者徹底了解智能合約是什么以及它們如何影響現代商業的交易方式,我們準備了本指南。 集中商業模式正在給去中心化的模式讓路 傳統的商業關系模型都是集中式的,始終存...

    PumpkinDylan 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<