今天主要介紹 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 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!
Diamond Pattern 還包含兩種預寫的 facets:
DiamondLoupeFacet:
// 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:
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);
}
我們宣告的任何狀態變數或者資料結構,預期中在 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 方便於讓 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;
}
最後歡迎大家拍打餵食大學生
0x2b83c71A59b926137D3E1f37EF20394d0495d72d