這兩天會快速地介紹三種 Proxy,當然現在也不只有這幾種 Proxy 模式(可見這兩天 Reference Others 處),我只是挑一些很常見的來講而已!
Minimal Proxy 的目標是在同樣一段程式碼或者一份合約會被 Deploy 數次時,能否透過一個標準來節省將同樣的 bytecode 多次存在鏈上的步驟,以模組化和節省成本為設計目標,Minimal Proxy 便出現了。相關的實作可以在 optionality/clone-factory 找到。
Minimal Proxy 在許多專案的合約中也會叫做:Clone Factory、Proxy Factory
簡單來說實作方式是我們可以 deploy minimal proxy contract 來指向已經實作函式 body 的合約地址,如此一來也能透過 delegatecall
把 state 存在 minimal proxy contract 上。一個知名的例子是 Uniswap 的流動池,每一個 pool 都可以指向真正實作了 liquity pool function 的那一個合約。
當我們在 minimal proxy contract 中呼叫了 createClone()
以後就會製造出一個指向 Implementation Contract 的 Proxy Contract。
Minimal Proxy 和可升級模式的 Proxy 其實沒什麼關係,只是一種最小化代理合約的 Patterns,在 Minimal Proxy Contract 並不會實作換 code 或換 address 的程式碼,只有初始設定的 creation code、implementation contract address、delegatecall 使用而已。
資料流的呼叫方式為:
我們可以看一下 EIP-1167: Minimal Proxy Contract 提供的例子,以下是一個 minimal proxy contract 的程式碼:
pragma solidity ^0.4.23;
/*
The MIT License (MIT)
*/
//solhint-disable max-line-length
//solhint-disable no-inline-assembly
contract CloneFactory {
function createClone(address target) internal returns (address result) {
bytes20 targetBytes = bytes20(target);
assembly {
let clone := mload(0x40)
mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(clone, 0x14), targetBytes)
mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
result := create(0, clone, 0x37)
}
}
}
0x40
是一個魔法 slot,是一個 free memory pointer,會儲存當前的 free memory 位置。
而這段程式碼經過編譯之後得到真正部署到鏈上的 bytecode:
0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3
這段 bytecode 主要由三個部分構成:
3d602d80600a3d3981f3
bebebebebebebebebebebebebebebebebebebebe
5af43d82803e903d91602b57fd5bf3
我們可以發現 Minimal Proxy Contract 中沒有 constructor 也沒有初始化的宣告,因為 Creation Code 的目的為:Copy the runtime code into memory and return it。這邊的 runtime code 指的是當我們呼叫了合約中的函式,則這段程式碼就會被執行。
複習一下可升級合約的模式:
delegatecall
EOA ----------> Proxy -----------> Implementation
(storage layer) (logic layer)
原本在 OpenZeppelin 中的 proxy 是 Transparant Proxy,而最近他們也轉向推薦使用 UUPS Proxies,可以讓整個系統設計更輕量化與多變。EIP-1822: Universal Upgradeable Proxy Standard (UUPS) 把 Proxy 中原本 Transparant Proxy 中會定義的「升級邏輯」移動到 Implementation Contract 中。也就是說同樣的介面(內容)之下,UUPS 可以省下部署 Admin、升級邏輯的部分 Gas。而由於升級於否被放在實作合約中,其中的安全機制為在 implementation 中加入一個 flag mechanism 來確保當前的升級函式能否被執行。
一個 UUPS 合約中的 Logic Contract 有兩個重要元素:
initialize()
:由於在 upgradable contracts 中沒有 constructor
的存在,這邊 initialize()
會代替 constructor
的作用。_authorizeUpgrade()
:讓升級可行,也就是上面提到的 flag mechanism。同時如果我們繼承了這三個 Library 可以幫助建立 Proxy 並管理 UUPS 的合約們:
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"
在使用 Hardhat-upgrades 時記得要使用
kind:'uups'
來指定 UUPS Proxy。
最後歡迎大家拍打餵食大學生
0x2b83c71A59b926137D3E1f37EF20394d0495d72d