5

可升級合約介紹 - 鑽石合約(EIP-2535 Diamond standard)

 3 years ago
source link: https://medium.com/taipei-ethereum-meetup/%E5%8F%AF%E5%8D%87%E7%B4%9A%E5%90%88%E7%B4%84%E4%BB%8B%E7%B4%B9-%E9%91%BD%E7%9F%B3%E5%90%88%E7%B4%84-eip-2535-diamond-standard-b12cbcb25ea2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
  • diamond:合約本體,是一個代理合約,無商業邏輯
  • facet:延伸的合約(實際商業邏輯實作的合約)
  • loupe:也是一個 facet,負責查詢的功能。可查詢此 diamond所提供的 facetfacet所提供的函式
  • diamondCut:一組函式,用來管理(增加/取代/減少)此 diamond合約所支援的功能

Loupe

直接來看 loupe的介面,從宣告就能很清楚暸解 diamond合約的實作方式,loupe宣告了一個結構 FacetFacet結構包含一個地址及 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

至於 facetdiamond合約上的註冊或是修改,就由 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 的方式處理。

Image for post
Image for post
https://eips.ethereum.org/EIPS/eip-2535#diagrams

diamond的設計中,每個 facet合約都是獨立的,因此可以重複使用(跟library 的概念一樣)

Image for post
Image for post
https://eips.ethereum.org/EIPS/eip-2535#diagrams

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK