Uniswap Permit2 實作與設計

Charles Jhong
Taipei Ethereum Meetup
9 min readJan 6, 2023

Uniswap 團隊在去年十一月的時候公佈了兩個新合約,在 DEX 生態中拋出了一個新的組合,試著透過一站式的高度整合提供更好的用戶體驗,其中 Permit2 合約是達成這個目標的一個重要基礎,本文將針對 Permit2 的設計與實作進行探討。

ERC20 授權痛點

用戶在面對新的 Dapp 時,通常會面臨以下幾個關於 ERC20 代幣授權的痛點

  1. 每個 Dapp 及每個 Token 都需要獨立授權,意味著用戶要發送授權交易到區塊鏈,步驟繁瑣且消耗手續費
  2. 為求方便,每次授權通常都給最大值 (uint256 max)
  3. Dapp 合約若有漏洞被開採,用戶需要盡快 revoke 授權,否則可能會丟失資產

為了解決上述問題,開發者提出 EIP-2612 使用 Permit 簽名的形式來取代呼叫 approve(),此設計可以有效解決以上痛點,但許多市場主流的代幣已經相當古老,而且合約是不可升級的設計,因此該方式並沒有辦法立即解決眼前的議題。

然而事情其實還是有轉圜的餘地,雖然原生的代幣不支援 permit,但如果能夠先授權給一個獨立的新合約,由該合約來做 permit 簽章驗證,那麼是否就達到了相同的目的?於是有了 Permit2 的誕生。

Permit2 — 外掛式的 Permit

Uniswap 的 Permit2 合約最主要的目是在解決 ERC20 授權的痛點,同時兼容無法升級的老舊代幣合約。作法上主要是以一個外掛式的 permit 合約來實現,並具備以下特色

  1. 支援任意 ERC20 token,不論該 Token 本身是否支援 permit
  2. 支援 EIP-1271,合約錢包能夠驗證簽章
  3. 支援 batch 呼叫
  4. 透過 nonce 機制允許用戶 cancel 已經簽出去的 pending permit

Permit2 實作

Permit2 繼承了兩個子合約,分別為 SignatureTransferAllowanceTransfer。用戶最一開始需要先授權代幣給 Permit2 合約,而未來的代幣轉帳就可以由 Permit2 來進行驗簽及管理。

Permit2 定義了簽章內容的各式資料結構,原則上都與 EIP-2612 中所定義的欄位大同小異,因此不另外做介紹。

SignatureTransfer (signature-based transfers)

用戶可以提供一次性的轉帳 permit 簽章給 Dapp 使用,Dapp 合約呼叫 Permit2 進行轉帳時夾帶用戶的 permit 簽章,這類的場景即是由 SignatureTransfer 負責。

SignatureTransfer 流程

合約介面主要就是帶入 transfer 相關參數以及 permit 簽名,值得一提的是 witness 這個欄位,實際作用是一個保留給 Dapp 使用的簽章欄位,可用於區分訂單 id 或者是 swap hash,避免 permit 被惡意搶跑,例如我簽了 100 USDC 的 permit 目的是想要 swap 換成 WETH,結果一個攻擊者拿著我的 permit 簽章執行了另一個 USDC <-> DOGE 的 swap。這部分主要看串接合約的實作邏輯來決定要如何利用 bytes32 witness 這個欄位。

/// @notice transferFrom with permit
function permitTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes calldata signature
) external;

/// @notice [batch] transferFrom with permit
function permitTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes calldata signature
) external;

/// @notice transferFrom with permit and witness
function permitWitnessTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes32 witness,
string calldata witnessTypeString,
bytes calldata signature
) external;

/// @notice [batch] transferFrom with permit and witness
function permitWitnessTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes32 witness,
string calldata witnessTypeString,
bytes calldata signature
) external;

/// @notice overwrite nonce to cancel pending permits
function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external;

AllowanceTransfer (signature-based approvals)

transfer with permit 的使用場景需要用戶每一次都在線做 permit 簽名,不過這樣在線的要求對於有些應用場景並不適合,因此 Permit2 仍支援原本 ERC20 授權的使用場景,也就是用戶先授權一個額度,擁有授權的合約可以直接呼叫 transferFrom(),而 AllowanceTransfer 作法上的差異主要是用戶可以不用發交易呼叫 approve(),而是透過簽章驗證的方式來做額度授權。

AllowanceTransfer 流程

介面的設計其實類似於 EIP-2612 的概念,除了基本的授權與轉帳以外,也支援 cancel 以及歸零的呼叫。

/// @notice legacy ERC20 approve function
function approve(address token, address spender, uint160 amount, uint48 expiration) external;

/// @notice approve with permit (single/batch)
function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;
function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external;

/// @notice legacy ERC20 transferFrom function (single/batch)
function transferFrom(address from, address to, uint160 amount, address token) external;
function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external;

/// @notice set allowance amount to zero
function lockdown(TokenSpenderPair[] calldata approvals) external;

/// @notice overwrite nonce to cancel pending permits
function invalidateNonces(address token, address spender, uint48 newNonce) external;

Uniswap 主導優勢

其實類似於 Permit2 的設計概念存在許久,也有被一些 DEX 採用,不過 Permit2 在一開始的設定就是希望能成為代幣授權的託管合約,並且能夠廣泛被各個 Dapp 串接使用,要能達成這個目的的前提是能夠得到多數用戶的信任與授權,而 Uniswap 在行業裡的角色實質上是有能力做到這樣的號召力,一同發表的 Universal Router 就已經整合了 Pertmi2 合約,換句話說,用戶若要使用新的 Universal Router,就需要先授權 Permit2 合約,擁有越多用戶的代幣授權,則越能夠吸引其他 Dapp 來串接。

除此之外,若要乘載大量用戶的代幣授權,合約安全勢必是一大信任的關鍵,Permit2 承襲了 Uniswap 的風格,採用不可升級的設計,同時由 Chainsecurity 與 ABDK 完成審計,並且附上完善的文件與 SDK,也發佈了 bounty program,是相當成熟的開源合約。

過去 CEX 託管了用戶的資產,有鑒於近期 FTX 事件及一連串的連鎖效應,許多用戶開始對中心化託管產生了不信任,而此時對於握有用戶 token approval 的 DEX 則是絕佳的發揮機會,我認為,讓用戶跳脫傳統中心化託管的模式,同時與用戶體驗及成本取得一個平衡,是 DEX 往下發展的一個重要的基石。

--

--