地址、ABI 與可升級合約

NIC Lin
Taipei Ethereum Meetup
10 min readMay 24, 2022

本篇介紹地址和 ABI 之間的關係,以及要如何去和可升級合約互動

https://flic.kr/p/9KXBHH

目標受眾是新手或是不熟悉合約互動的使用者

地址與 ABI

當你透過 etherscan 瀏覽鏈上資訊時,你的目標通常會是區塊、交易或地址。地址可以是一個背後是由私鑰所控制的 Externally Owned Account (EOA),也可以是一個合約。如果今天地址上是一個合約,則你會需要知道它有什麼功能才能和它互動,例如你要去 Uniswap V2 交易時,你可能會呼叫的函式名稱是 swapExactTokensForTokens,用代幣 A 換代幣 B。

一個合約有什麼功能,或是說這個合約的長相,會用 ABI 來定義,例如含有 swapExactTokensForTokens 功能的 ABI 會長這樣:

[
{
"inputs": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOutMin",
"type": "uint256"
},
{
"internalType": "address[]",
"name": "path",
"type": "address[]"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "swapExactTokensForTokens",
"outputs": [
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"stateMutability": "nonpayable",
"type": "function
}
]

ABI 定義了函式的介面,包含了最重要的函式名稱及 input 與 output。有了介面,工具例如 ethersjs 才知道要怎麼把你要呼叫的函式和參數組裝起來送到鏈上。

但要記住,ABI 不是和地址綁定的。底層協議沒有規定說地址 A 只能由某一份 ABI X 裡定義的函式才能呼叫,你可以拿另一份 ABI Y,請 etherjs 幫你組出 Y 裡面的函式和參數,然後送到地址 A 的合約去執行。這是可以的,只是會不會執行成功就不一定了,如果地址 A 的合約認不出你要呼叫的函式,交易就會失敗。

Uniswap V2 認得 swapExactTokensForTokens,但不一定認得 deposit

你可能會覺得奇怪:拿 ABI Y 裡的函式去執行地址 A 的合約,這樣做有什麼意義?接下來要介紹的可升級合約就會遇到這樣的情境了。

可升級合約

可升級合約的實現方式有很多種,但會需要可升級合約的原因都是因為部署在鏈上的合約不能隨意更改且合約大小有限制。如果團隊預期未來會不斷更新功能但又不希望每次都部署新的合約,導致使用者要一直切換互動的地址或是團隊維護困難,就會選擇走可升級合約的方式。

可升級合約由 Proxy 合約和 Logic (或稱 Implementation)合約組成,Proxy 合約是進入點,Logic 合約則是實際寫有商業邏輯的合約。使用者的交易是送往 Proxy 合約、和 Proxy 合約互動,Proxy 合約再去問 Logic 合約該怎麼處理使用者的請求。Proxy 合約本身很簡單,它基本上只做兩件事:(1) 紀錄 Logic 合約的地址,以及 (2) 在使用者呼叫時,去問 Logic 合約該怎麼處理使用者的請求。

Proxy 合約和 Logic 合約會是兩個不同的合約,使用者都和 Proxy 合約互動

delegatecall

這邊需要稍微修改一下前面一段的內容,前面一段裡描述 Proxy 合約和 Logic 合約之間的戶動是用「Proxy 合約去問 Logic 合約該怎麼處理使用者的請求」來描述,但實際上應該要用「Proxy 合約去請 Logic 合約附身在自己身上」來描述會更精確一點。這個附身功能是由一個叫 delegatecall 的 opcode 來觸發,但這裡不會深入細節。

對使用者來說,他互動的還是 0xabcd 這個地址,但對方是以 Logic 合約的邏輯在執行並回應

結合前面提到的「拿 ABI Y 裡的函式去執行地址 A 的合約」,讀者應該已經想到,使用者拿的 ABI 會是 Logic 合約的 ABI 而不是 Proxy 合約的 ABI:使用者對 Proxy 合約呼叫 Logic 合約的函式。但當然如果使用者對 Proxy 合約呼叫一個 Logic 合約或 Proxy 合約都認不出來的函式,交易還是會失敗。

接下來會以實際的例子來示範該如何使用地址和 ABI 這兩個資訊來和一般合約及可升級合約互動。

Etherscan 操作

大家最常用的 Etherscan,在合約頁面就提供了很方便的介面來讓使用者和合約互動。

使用者能直接在 Etherscan 的 Uniswap V2 合約頁面直接操作

Etherscan 能知道一個合約有哪些函式可以呼叫是因為這個合約有上傳它的原始碼,原始碼編譯完後會順便得到該合約的 ABI,因此 Etherscan 可以知道這個合約有哪些函式可以讓使用者呼叫。

如果合約有上傳原始碼,則代碼的頁面可以看到原始碼及 ABI

註:如果合約沒有上傳原始碼,Etherscan 就不會知道它的 ABI,也就沒辦法讓使用者在 Etherscan 上和該合約互動,必須要另外找工具。

那如果是一個可升級合約呢?

Etherscan 怎麼知道是可升級合約

我們以 Arbitrum 上的 USDT 合約為例,你在 Contract 頁面會看到多了兩個按鈕:Read as Proxy 及 Write as Proxy。多了這兩個按鈕即是告訴你,這是一個可升級合約,如果你要執行 Logic 合約的函式,請透過 Read as Proxy 和 Write as Proxy 裡的函式。

Write as Proxy 裡可以看到是 USDT 會有的函式

註:如果你用原本的 Read 和 Write,表示你要執行 Proxy 合約本身的函式。你可以看到在這個例子裡,Write 基本上只有五個函式,而且看起來都不像是一個 USDT 要有的函式。這些都是給 Proxy 合約管理員執行的函式。

Write Contract 裡的是給管理員用的函式
Read/Write as Proxy 看到的是 Logic 合約的函式,Read/Write 則是看到 Proxy 合約本身的函式

但 Etherscan 怎麼知道這是一個可升級合約?其實這是因為該 Proxy 合約有符合特定 EIP 所規定的實作方式,例如此例是以 EIP 1967 這個標準去實作,Etherscan 可以透過檢查一些該標準所訂定的規範來知道這是不是一個 EIP 1967 Proxy 合約。確認是 EIP1967 Proxy 合約後,Etherscan 就可以自動去拉它的 Logic 合約的 ABI 過來,然後將裡面的函式呈現在 Read as Proxy 及 Write as Proxy 裡。

註:還有其他可升級合約 EIP,例如 Compound 的 cToken 則是使用 EIP 897

如果合約沒有上傳原始碼呢?或是不合規範的可升級合約?

那我們就會需要利用其他的工具,例如 MyCrypto、MyEtherWallet 或 Remix。但最重要的是,你要有 ABI!

如果項目方沒有開源原始碼沒關係,大多數情況我們也不會去關心原始碼怎麼運作,我們只要知道 ABI、知道合約的長相就好。至於要怎麼找到項目方合約的 ABI 又是另外一回事了,基本上項目方最少還是會開源實際部署的合約地址及 ABI,可能放在他們的 repository 裡、documentation 裡或是在官方網頁上。

接下來會以 MyCrypto 為例,示範如何利用地址和 ABI 兩個資訊去和可升級合約互動。但記得,你至少要知道你要互動的合約地址及其 ABI(而且不是 Proxy 合約的 ABI)!

如何透過 MyCrypto 操作可升級合約

首先進到 MyCrypto 的 Interact with Contracts 頁面:https://app.mycrypto.com/interact-with-contracts

接著選擇網路、填入合約地址。這邊因為 MyCrypto 預設網路裡沒支援 Arbitrum,所以改成以另一個 xLON 合約為例 (Shameless Plug):0xf88506B0F1d30056B9e5580668D5875b9cd30F23

填入地址後,如果它有在 Etherscan 上驗證過,MyCrypto 會自動把 ABI 抓下來。當你按下 Interact with Contract 後,你可以看到 Read/Write Contract 的下拉選單,但馬上就會發現這五個函式和上面 USDT 合約的函式是一樣的,都是給 Proxy 合約管理員用的函式。

MyCrypto 直接拉下來的是 Proxy 合約的 ABI

所以這表示我們要自己貼上 Logic 合約的 ABI。xLON 的 Logic 合約地址裡可以看到它的 ABI

直接點擊複製 ABI

貼上 Logic 合約 ABI 後,下拉選單裡就會變成是預期的函式了。

如何透過 Gnosis Safe 操作可升級合約

Gnosis Safe 的介面也差不多,點擊 New Transaction 並選擇 Contract Interaction 就會進到填地址和 ABI 的頁面:

這裡以 Arbitrum 上的 USDT 為例,填入地址後一樣會看到 Proxy 合約給管理員操作的函式。

換上 Logic 合約的 ABI 後,就會看到正常的 USDT 函式了。

只要有地址和 ABI ,你就可以利用其他工具去和合約互動,不必只能仰賴 Etherscan。

Special thanks to Ping Chen , Peter Lai and Chang-Wu Chen for reviewing and improving this post

--

--