Ethereum Proxy Contract

微微介紹

(DELEGATECALL, Fallback Function, Contract Storage Model)

為什麼要用 proxy contract

  • 減少 deployment 的 transaction fee
  • 利用 proxy 的機制可以設計能夠 upgrade 的合約

Proxy

Contract

Logic

Contract

User

先來看一下合約

pragma solidity >=0.4.0 <=0.6.0;

contract ExampleDapp {
    string dapp_name; // state variable

    // Get Function
    function read_name() public view returns(string) {
        return dapp_name;
    }

    // Set Function
    function update_name(string value) public {
        dapp_name = value;
    }
}

→ 會存在合約 storage 內

→ 改動合約 storage

→ 讀取合約 storage

  • 合約有自己的 storage
  • 合約內的 functions 會存取 storage

使用 proxy 的話

在 A 合約的 context,執行 B 合約的 function

這個主要是透過 EVM 的 DELEGATECALL 來達成

B 的 function 執行時 存取的是 A 的變數

EIP-1167 Minimal Proxy

把所有的 function call 都交給 logic contract 執行

它的 bytecode 只有這樣

其中 bebebebebebebebebebebebebebebebebebebebe 會是 logic contract 的地址

363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3

要怎麼看懂這個合約

不過有很讚的 OpenZeppelin 教大家看

因為他是直接寫 bytecode 所以不好懂

原理就是只寫一個 fallback function

把所有 message call 都用 DELEGATECALL 的方式

轉給寫死的 logic contract 來執行

(這個是 OpCode 的好讀版)

Fallback Function

收到的 call 沒有對應的 function

就會執行 fallback function

甚至是沒有帶 data,單純送錢的 call

也執行 fallback function

題外話 The DAO hack

大家知道現在的以太坊區塊鏈

其實有被硬改過

有些歷史被抹掉了 QQ

contract TheDao {
  function refund() {
    check(sender);
    sender.send(value);
    update(sender);
  }  
}
contract Hack {
  function fallback() {
    if (canRefund) {
      TheDao.refund();
    }
  }
}

用 minimal proxy 的好處

bytecode 很短,部署合約很便宜

如果需要大量一模一樣邏輯

但又要不同地址、不同狀態的合約

用 proxy 就很省

如果想要合約可以 upgrade

就不能用 minimal proxy 的方式

因為它把 logic contract 的地址寫死在 bytecode 了

有點像是寫死在 web server 的程式碼裡面這樣

我們知道合約程式碼是不能修改的

那有可能可以把 logic contract 的地址

寫在 storage (db) 裡面 讓它可以被改嗎

可以啊 不過

可能會怎麼寫

這樣可以嗎

不行

contract Proxy {
  address public logic;
  
  function upgrade(address newLogic) {
    logic = newLogic;
  }
  
  function () {
      ...
      ...delegatecall...
      ...
    }
  }
}
contract Logic {
  uint256 public a;
  uint256 public b;
  
  function A() {
    ...
  }
  
  function B() {
    ...
  }
}

合約的 storage model

storage 可以想成一個 sparse 的 array

solidity 的 compliler

會照 variable 宣告的順序

指定它們的 index (slot)

所以 slot 會衝到

proxy 自己保留的變數

要小心不要讓 logic contract 用到同一個 slot

所以要往後放

像這樣把 upgrade 的邏輯

保留在 proxy 合約這邊

其它交給 logic 合約的 pattern

非常常見

目前也很多不同作法

來看看 USDC 合約

另外補充

function 看起來不會衝到

(因為 proxy 優先 沒有才 fallback)

但是要注意一個問題

看起來不同的 function 可能會有一樣的 signature

(function clashing)

合約程式編譯完

function 會有一個 id (signature)

這個 id 是經過一個 hash 然後取前 4 個 bytes 來的

例如 →→→

大家都用 proxy,要怎麼看得出來這個合約在幹麻

(Minimal Proxy 可以直接看 bytecode 知道 implementation 合約地址)

參考資料

Made with Slides.com