iT邦幫忙

2022 iThome 鐵人賽

DAY 16
0
Web 3

Smart Contract Development Breakdown系列 第 16

Day 16 - Contract Proxy: Diamond Pattern

  • 分享至 

  • xImage
  •  

Contract Proxy: Diamond Pattern

Synchronization Link Tree


Intro.

今天主要介紹 Diamond Pattern,Diamond Pattern 主要來自於 EIP-2535: Diamonds, Multi-Facet Proxy。簡單來說 EIP-2535 是一個管理複雜、大型智能合約專案的一種方法,可以使用一個 proxy contract 對多個不同的合約(不同地址)進行呼叫。

在這樣的 Diamond Pattern 中會有一個主合約,他不會有太多功能型的程式碼而是主要利用一個 fallback function 來對其他合約進行呼叫,呼叫過程也和其他 pattern 一樣,透過 function selector 來控制對象並且使用 delegate call 來戳函式。

Diamond Pattern

Diamond Pattern 的升級方法被稱作 Diamond Cut,可以透過 EOA 決定的參數與方式來決定要怎麼對「這顆鑽石」切割、取代、增加、減少程式碼。Diamond 讓專案可以升級部分合約而非直接把整個合約的地址進行替換。

昨天提到的普通可升級合約的模式:

                         delegatecall
EOA  ---------->  Proxy  -----------> Implementation
             (storage layer)          (logic layer)

而 Diamonds 的可升級合約模式:

                           delegatecall
EOA  ---------->  Diamond  -----------> Facet A, B, C....
             (storage layer)             (logic layer)

facet 代表 Logic 合約,通常會有很多個。而 Diamond 就是我們提到的主合約,除了使用 delegate call 來呼叫以 function delector 指定的合約函式,還會簡單的部署包含新的 logic 的 facet 並儲存他們的地址,如果我們單一取代一個 facet 就可以取代一個部分的實作部分,可以避免過往直接重新部署新的 Logic Contract!

Loupe and Cut

Diamond Pattern 還包含兩種預寫的 facets:

DiamondLoupeFacet:

  • 就像是一個函式列表一樣的存在,能夠查詢 facet 與其內部的實作函式(function selector),也能夠幫助查尋當前 diamond 中的狀態
// A loupe is a small magnifying glass used to look at diamonds.
// These functions look at diamonds
interface IDiamondLoupe {
    /// These functions are expected to be called frequently
    /// by tools.

    struct Facet {
        address facetAddress;
        bytes4[] functionSelectors;
    }

    /// @notice Gets all facet addresses and their four byte function selectors.
    /// @return facets_ Facet
    function facets() external view returns (Facet[] memory facets_);

    /// @notice Gets all the function selectors supported by a specific facet.
    /// @param _facet The facet address.
    /// @return facetFunctionSelectors_
    function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_);

    /// @notice Get all the facet addresses used by a diamond.
    /// @return facetAddresses_
    function facetAddresses() external view returns (address[] memory facetAddresses_);

    /// @notice Gets the facet that supports the given selector.
    /// @dev If facet is not found return address(0).
    /// @param _functionSelector The function selector.
    /// @return facetAddress_ The facet address.
    function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}

DiamondCutFacet:

  • 所謂的升級就出現在這裡,能夠用來增刪或改 Diamond 想要實作的函式功能
interface IDiamondCut {
    enum FacetCutAction {Add, Replace, Remove}
    // Add=0, Replace=1, Remove=2

    struct FacetCut {
        address facetAddress;
        FacetCutAction action;
        bytes4[] functionSelectors;
    }

    /// @notice Add/replace/remove any number of functions and optionally execute
    ///         a function with delegatecall
    /// @param _diamondCut Contains the facet addresses and function selectors
    /// @param _init The address of the contract or facet to execute _calldata
    /// @param _calldata A function call, including function selector and arguments
    ///                  _calldata is executed with delegatecall on _init
    function diamondCut(
        FacetCut[] calldata _diamondCut,
        address _init,
        bytes calldata _calldata
    ) external;

    event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}

Storage layout

  • 所有的狀態變數都存在最根源的 Diamond Proxy 合約裡面,同時 diamond proxy 也會記錄每個 data 的 storage。
  • 任何的資料結構和狀態變數是在 facet 被定義的,即便儲存在 Diamond Contract 中不同的 storage 位置,任何 facet 都能使用他們。
  • 使用 Diamond Storage facets 可以宣告 Diamond 自己的狀態變數,而不會跟他們的 facet 的變數產生資料衝突,也就是說 Diamond Storage 可以是獨立的,不需要在乎或者去連結其他 facet。
  • 每一個 facet 被開發的時候都不用擔心會跟其他的 facet 產生 collision 或在乎其他 facet 在 Diamond 母合約中的記憶體配置。

Diamond storage

我們宣告的任何狀態變數或者資料結構,預期中在 diamond 升級時(出現新的 facet)就會出現問題。而
Diamond Storage 解決這個問題的方式是他會特別指明你的資料在母合約中應該存在哪裡(或說從哪裡開始存)。他會使用 hash 過後的值作為 mapping 對應到在 Diamond 中儲存的 storage 位置(storage key -> storage pointer)。

當有一個外部函式呼叫了 Diamond,他的 fallback function 就會被執行然後去這樣一個 mapping 資料結構中找到該呼叫的人(變數),找到時可以對應到他實際上的 storage 位置然後做更新。

// A contract that implements Diamond Storage.
library LibA {

  // This struct contains state variables we care about.
  struct DiamondStorage {
    address owner;
    bytes32 dataA;
  }

  // Returns the struct from a specified position in contract storage
  // ds is short for DiamondStorage
  function diamondStorage() internal pure returns(DiamondStorage storage ds) {
    // Specifies a random position in contract storage
    // This can be done with a keccak256 hash of a unique string as is
    // done here or other schemes can be used such as this: 
    // bytes32 storagePosition = keccak256(abi.encodePacked(ERC1155.interfaceId, ERC1155.name, address(this)));
    bytes32 storagePosition = keccak256("diamond.storage.LibA");
    // Set the position of our struct in contract storage
    assembly {ds.slot := storagePosition}
  }
}

// Our facet uses the Diamond Storage defined above.
contract FacetA {

  function setDataA(bytes32 _dataA) external {
    LibA.DiamondStorage storage ds = LibA.diamondStorage();
    require(ds.owner == msg.sender, "Must be owner.");
    ds.dataA = _dataA;
  }

  function getDataA() external view returns (bytes32) {
    return LibA.diamondStorage().dataA;
  }
}

AppStorage

AppStorage 方便於讓 facets 彼此溝通 state variable,藉由宣告一個 AppStorage 這樣的資料結構,來把所有將要使用的狀態變數記錄下來,AppStorage 是最先被宣告的,所以在合約中 AppStorage 記憶體使用永遠是從 0 開始的。

import "./AppStorage.sol"

contract StakingFacet {
  AppStorage internal s;

  function myFacetFunction(uint256 _nextVar) external {
    s.total = s.firstVar + _nextVar;
  }

Closing

Reference


最後歡迎大家拍打餵食大學生0x2b83c71A59b926137D3E1f37EF20394d0495d72d


上一篇
Day 15 - Contract Proxy: Minimal Proxies & UUPS
下一篇
Day 17 - Cryptography
系列文
Smart Contract Development Breakdown30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言