📜 [專欄新文章] 可升級合約介紹 - 鑽石合約(EIP-2535 Diamond standard)
✍️ Kimi Wu
📥 歡迎投稿: https://medium.com/taipei-ethereum-meetup #徵技術分享文 #使用心得 #教學文 #medium
Photo by Evie S. on Unsplash
前言
可升級合約簡單來說是透過 proxy contract(代理合約)來達成,藉由代理合約去呼叫欲執行的合約,若要升級,則把代理合約中的指向的地址換為新的合約地址即可。而執行的方式則是透過 delegateCall,但 delegateCall 不會更動目標合約的狀態。所以要怎麼處理變數,就是一門學問了。
舉例來說,contract B 有個變數 uint256 x,初始值為 0, 而 function setX(uint256),可以改變 x 的值。proxy contract A 使用 delegatecall 呼叫 contract B 的 setX(10),交易結束後,contract B中的 x 依然還是 0。
OpenZeppelin 提出了三種實作方式,可以做到可升級合約,細節可參考 Proxy Patterns,而最終的實作選用了 Unstructured Storage的這個方式,這種方式對於開發較友善,開發時不需特別處理 state variables(不過升級時就需要特別注意了)。而這篇主要是介紹 Diamond standard,OpenZeppelin 的可升級合約就不多做介紹。
USDC V2 : Upgrading a multi-billion dollar ERC-20 token 詳細地介紹代理合約跟變數儲存之間的關係,不了解升級合約的原理,建議先看看。
鑽石合約
名詞介紹
diamond:合約本體,是一個代理合約,無商業邏輯
facet:延伸的合約(實際商業邏輯實作的合約)
loupe:也是一個 facet,負責查詢的功能。可查詢此 diamond所提供的 facet與facet所提供的函式
diamondCut:一組函式,用來管理(增加/取代/減少)此 diamond合約所支援的功能
Loupe
直接來看 loupe的介面,從宣告就能很清楚暸解 diamond合約的實作方式,loupe宣告了一個結構 Facet,Facet結構包含一個地址及 function selector 陣列,所以我們只需要記錄一個 Facet陣列就可以得知這個 diamond 合約有多少個延伸合約及所支援的功能(loupe只定義結構,而實際變數是存在diamon合約中的)。也就是 diamond合約中只記錄延伸合約的地址及其支援的 function selectors,及少數 diamond合約的管理邏輯,並無商業邏輯,因此可以外掛非常非常多的合約上去(就像一個Hub),也就可以突破一個合約只有24K的限制。
// A loupe is a small magnifying glass used to look at diamonds.interface IDiamondLoupe { struct Facet { address facetAddress; bytes4[] functionSelectors; } function facets() external view returns (Facet[] memory facets_); function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_); function facetAddresses() external view returns (address[] memory facetAddresses_); function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);}
DiamondCut
至於 facet在 diamond合約上的註冊或是修改,就由 diamondCut負責,從以下程式碼可以清楚瞭解其功能(EIP中有規範,每次改變都需要發送DiamondCut事件)
interface IDiamondCut { enum FacetCutAction {Add, Replace, Remove} // Add=0, Replace=1, Remove=2 struct FacetCut { address facetAddress; FacetCutAction action; bytes4[] functionSelectors; } function diamondCut( FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata ) external; event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);}
Diamond合約
接下來就是最核心的部分 — diamond本體合約。以下是官方的範例,方法上跟 OpenZeppelin 一樣使用 fallback 函式跟 delegateCall 。
呼叫合約所不支援的函式,就會去執行 fallback 函式,fallback 函式中再透過 delegateCall 呼叫 facet 合約相對應的函式
fallback() external payable { address facet = selectorTofacet[msg.sig]; require(facet != address(0)); // Execute external function from facet using delegatecall and return any value. assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 {revert(0, returndatasize())} default {return (0, returndatasize())} }}
主要的差異在於變數的處理,OpenZepplin 是針對單一合約設計的代理合約(也就是每個合約都有自己的代理合約),所以無法處理單一代理合約儲存多個合約的變數(state variables)的狀況(後有圖例)。先由官方的範例程式來了解是怎麼處理變數的
在官方的範例中,都是以更改合約 owner 為例子
首先看到 DimaondStorage這個結構,結構中的前面三個變數都是在維持 diamond合約的運作(同上面loupe的範例),最後一個變數 contractOwner就是我們商業邏輯中所需的變數。
接著看到 function diamondStorage(),取變數的方式就跟OpenZeppelin 儲存特定變數方式一樣(EIP-1967),是把變數存到一個遠方不會跟其他變數碰撞到的位置,在這裡就是從 DIMOND_STORAGE_POSITION 這個 storage slot 讀取。
在實作上就可以有 LibDiamond1 ,宣告DIMOND_STORAGE_POSITION1=keccak256("diamond.standard.diamond.storage1") ,負責處理另一組的變數。藉由這種方式讓每個 facet合約有屬於自己合約的變數, facet合約間就不會互相影響。而最下方的 setContractOwner 是實際使用的範例。
library LibDiamond {
bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");
struct FacetAddressAndSelectorPosition { address facetAddress; uint16 selectorPosition; }
struct DiamondStorage { mapping(bytes4 => FacetAddressAndSelectorPosition) facetAddressAndSelectorPosition; bytes4[] selectors; mapping(bytes4 => bool) supportedInterfaces; // owner of the contract address contractOwner; }
function diamondStorage() internal pure returns (DiamondStorage storage ds) { bytes32 position = DIAMOND_STORAGE_POSITION; assembly { ds.slot := position } }
function setContractOwner(address _newOwner) internal { DiamondStorage storage ds = diamondStorage(); address previousOwner = ds.contractOwner; ds.contractOwner = _newOwner; emit OwnershipTransferred(previousOwner, _newOwner); }
每個 library 處理了一組或多組變數的存取, facet 合約透過 library 對變數做操作。也就是把變數存在diamond主體合約,延伸的 facet合約只處理邏輯,是透過 library 去操作變數。
下面圖中清楚地解釋了 facet合約,function selectors 與變數之間的關係,從最左上這邊有個 facets 的 map,紀錄了哪個 selector 在哪個合約中,例如func1, func2是 FacetA的函式。左下角宣告了變數,每組變數的存取如同上述 library 的方式處理。
https://eips.ethereum.org/EIPS/eip-2535#diagrams
在 diamond的設計中,每個 facet合約都是獨立的,因此可以重複使用(跟library 的概念一樣)
https://eips.ethereum.org/EIPS/eip-2535#diagrams
小結
diamond合約使用不同的設計來達成合約的可升級性,藉由這種Hub方式可隨時擴充/移除功能,讓合約不再受限於24KB的限制,此外充分的模組化,讓每次升級的範圍可以很小。最後,因為跟library一樣只處理邏輯,並無狀態儲存,所以可以重複被不同的diamond合約所使用。
雖然又不少好處,也是有些缺點。首先,術語名詞太多,facet, diamondCut, loupe等等(其實還有好幾個,不過沒有介紹到那些部分,所以沒有寫出來)。開發上不直覺,把變數跟邏輯拆開,若要再加上合約之間的繼承關係,容易搞混,不易維護。最後,gas的花費,在函式的讀取、呼叫,變數的存取、傳遞都會有不少的額外支出。Trail of Bits 專欄中有點出更多的缺陷 Good idea, bad design: How the Diamond standard falls short,不過作者也有反擊 Addressing Josselin Feist’s Concern’s of EIP-2535 Diamond Standard,有興趣的讀者可以自行看看、比較。
為了模組化及彈性,diamond合約在設計上有點太複雜(over engineering),會造成可讀性越差(這點也是Vyper誕生的原因之一),而可讀性越差就越容易產生bug、也越不容易抓到bug,而在defi專案中,一個小小的bug通常代表著大筆金額的損失 😱😱😱。
雖然如此,筆者還是覺得很酷,有些設計的思維仍然可以使用在自己的專案
ref:
EIP 2535
Diamond 實作
Addressing Josselin Feist’s Concern’s of EIP-2535 Diamond Standard
OpenZeppelin upgradeable contract
可升級合約介紹 - 鑽石合約(EIP-2535 Diamond standard) was originally published in Taipei Ethereum Meetup on Medium, where people are continuing the conversation by highlighting and responding to this story.
👏 歡迎轉載分享鼓掌
「switch hub差異」的推薦目錄:
switch hub差異 在 請問!Switch跟Hub的差別...我的認知這樣對嗎?? - Mobile01 的八卦
同一個HUB(或一組串並接的HUBs)所有的Port視為彼此相互連通的可以直接傳送訊號,而Switch HUB的每個Port並非直接連通,而是有通訊需求時才打開Switch( ... ... <看更多>
switch hub差異 在 [問題] Switch & Hub & Router 的差異? - 看板Network 的八卦
很抱歉,在這邊問個蠢問題,由於爬文沒有找到相關的文章想請教大家,常聽到的router switch Hub 有什麼不同? 謝謝!! -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◇ From: ... ... <看更多>
switch hub差異 在 Re: [問答] Switch Router 比較- 看板MIS - 批踢踢實業坊 的八卦
※ 引述《Cersei (Lannister)》之銘言:
[恕刪]
: 其實還是對這兩個有點模糊
: Router => OSI Layer 3
: Switch => OSI Layer 2
: 也就是Router 算是比Switch 有更強大的優點
: 至少多了 IP 防火牆 之類的
: 1. 那我想問Switch 有什麼優點是Router無法取代的嗎?
: 是因為port多嗎?
: 2. 假設只有"單一 1組IP" 連接Switch 就無法上網
: 僅能區域網路連線嗎?
: 3. 現在市面販售的 AP , 算是Layer 3應用吧?
: 4. 希望各位能再幫我補充概念
: 既然Router 這麼多功能
: 為何有些人還是買Switch ?
: 價格? port數?
: 謝謝~
嘖嘖.. Hmmm..
基本上在版眾面前感到有點班門弄斧,不過想說還是囉嗦點好了..
以下是十年前的印象+不太專業的經驗,就當作拋磚引玉,
如果有錯誤或有問題請重版眾幫忙了!
基本上從L1->L3... 要分辨很簡單.. 從笨到聰明分別敘述..
a) L1設備=>收到東西就送就對了.. 所以有兩種代表的Device,
Repeater是代表的設備,收到封包就往另一個Port送就對了。
Hub就是多Port的Repeater,只要收到訊號,
就直接送往其他Port就對了。這種東西都該進博物館,很久不見了。
缺點就是當兩個Port同時送東西的時候,Collision燈就會狂閃,
網路速度就會狂掉,因為線只有一條嘛。
b) L2設備=>聰明一些,他(只)可以利用來源和目的的MAC Address分清楚要送和不要送。
Bridge就是一個代表的設備,他收到封包會評估要不要往另一個Port送,
如果封包的目的端不是另一Port的話他就不送了。
L2 Switch就是多Port的Bridge,當其中一個Port發出封包時,
封包只會送到目的的Port,其他Port都不會送。
因此,只要來源及目的Port不同,同時收送資料是不會產生碰撞的。
當然多Port同時收送時,速度多少會有影響,這就是理想和實務的差距了。 xDa
So, 他的優勢是切割了碰撞空間,讓大家不容易打架。
c) L3設備=>傳說中的IP,透過各種Routing Protocol決定封包往哪送,
代表的設被當然就是Router,他不只決定封包要送到哪個Port,
更決定了要透過哪條路徑送到遠方的目的地、把各個子網路串在一起。
因為Router每個介面通常都拿不同的子網路IP,所以Router會切割廣播空間,
子網路中的廣播封包都會到Router為止。
至於L3 Switch,就可以看作是多Port的Router,當然功能較多,
某些比較深入的功能就不一定會面面俱到了,但是基本上就是如此。
以前CCNA考試超愛畫一堆Router/Switch/Hub...
然後問有幾個廣播空間幾個碰撞空間幾個子網路,搞清楚就沒事了。
至於你的問題,我想有一部分跟定義有關,
如果你提的Router,是在賣場買到的IP分享器,
我覺得有一點LP比雞腿,一點點啦。
IP分享器之所以會稱為Router,其實是因為他的路(ㄈㄣ)由(ㄒㄧㄤˇ)功能。
講更直白一點,就是會把封包在Lan Port和Wan Port切成兩個子網路,
Wan是實體IP的子網路,Lan是虛擬IP的子網路,讓封包在兩者間轉換IP/轉送,
這跟剛剛講的Router其實有些落差的,因為他只會把封包往Gateway丟,
通常不支援路由協定(某些高貴的分享器當然除外),也不知道封包要走哪條路,
而其他幾個Lan Port間的功能,其實就是Switch,
所以講更直白一些,他就是有IP轉送功能的Switch。
不過給一個深奧的名字應該有助於定價吧,我想! xDa
所以回到原來的問題:
1) Switch和Router能否互相取代?
比喻一下,暨然有中華郵政,那管理員是否就不需要分信了?
今天社區人太多時是否還是需要管理員協助分信?
能否要求管理員取代郵差的工作?
2) 假設只有一組IP連到Switch是否就無法上網,僅能區域網路連線?
這邊有定義問題,所謂的上網是單講TCP/IP網路嗎?
如果走IPX、NetBIOS一類不一定要拿IP的網路,是否算數?
哎呀扯遠了,在概念上,實體IP就是對外門牌,虛擬IP就是家裡的樓層房間,
沒有門牌要怎麼跟人家通訊呢?當然某些駭客手法偽裝別人的門牌就是另一回事了。
3) AP算第幾層喔?
有點尷尬捏,回去上面的敘述。
講無線的那段的話,就是無線的Switch,但是講到分享器這段的時候呢,
又扯到一些L3,所以怎麼分啊?新版的CCNA有明確規範嗎?哈。
(我多年前念的CCNA2.0、3.1記得好像都沒有特別提無線網路)
4) 有了Router幹嘛買Switch?
問題同第一題,但是另一方面,L3 Switch和Router,是否會互相取代?
在核心骨幹,通常功能越單純出問題的機會會越少,與其買一台電子花車,
不如買一台樸實穩定的設備,至少不用擔心一天到晚被半頁Call。
大概就是這樣,
班門弄斧結束,有任何錯誤歡迎大家指證。
Best Regards,
by ASimon
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 140.134.25.104
※ 文章網址: https://www.ptt.cc/bbs/MIS/M.1439499459.A.29E.html
... <看更多>