Paymaster 主要的職責在於代替使用者支付交易上鏈的 gas 費用,只要能提供這樣的服務,我們就可以稱之為 Paymaster。事實上,Paymaster 並不是 AA(Account Abstraction)被提出後才出現的概念,早在 AA 之前,市場上已經有許多產品(例如 Tokenlon)透過研發鏈下的 Relayer 服務,來協助使用者將交易上鏈,並且代替他們支付以原生代幣計價的 gas 費用,以提供更好的產品互動體驗。
但是,依賴於鏈下設施來提供 Paymaster 服務,會產生服務商綁定(vendor lock-in)的問題:
如果慣用的 Paymaster 服務商,因為不可抗因素而停止運作,使用者會完全喪失 Paymaster 的功能,承受服務商單點故障的風險。
各家 Paymaster 服務商會根據自己的業務需求,訂定出無法互相兼容的 API 規格,導致切換 Paymaster 服務商成本高昂。
AA 和 ERC-4337(以下簡稱 4337)的出現,為我們帶來了兩個主要改變。
第一,4337 統一了在鏈下定義鏈上操作的資料結構,在 4337 提案裡稱之為 UserOperation。鏈下資料結構有了統一的規範後,使用者可以降低對特定 Relayer 服務商的依賴,隨時切換到其他能夠處理 UserOperation 的 Relayer 服務。這些有能力處理 UserOperation 的 Relayer 服務,在 4337 提案裡稱之為 Bundler。
第二,4337 在鏈上處理交易的流程中(可參考 EntryPoint 合約),嵌入了 Paymaster 的抽象介面,讓 Paymaster 邏輯可以透過鏈上的智能合約實現,不只大幅降低了單點故障的風險,更開啟了鏈上可組合性的想像。
舉例來說,Paymaster 可以先代替使用者以原生代幣(例如 ETH)支付交易的 gas 費用,並在交易結束前,向使用者取回與代付原生代幣等值的 ERC-20 token(例如 USDT)。
總結來說,在 4337 之前,Paymaster 功能必須依賴於特定服務商所研發的鏈下 Relayer 服務;在 4337 之後,鏈下元件如 UserOperation 和 Bundler 有了統一的規範,Paymaster 變為純粹的鏈上智能合約,不只大幅降低了單點故障的風險,還能串接鏈上其他合約,實現更多元的應用場景。
UserOperation 資料結構中,定義了一個給 Paymaster 使用的欄位 paymasterAndData
:
// UserOperation
{
...
paymasterAndData: <paymaster address (20 bytes)><custom data for paymaster>
}
這個欄位前 20 bytes 為鏈上 Paymaster 合約的地址,使用者可以任意指定這筆交易想要使用哪個 Paymaster;在 20 bytes 的地址之後,可以附上任意長度的資料,視各個 Paymaster 合約需求來定義。特別注意的是,custom data 會直接接在 Paymaster 地址之後,以 Solidity 來說等同於:
paymasterAndData = abi.encodePacked(paymasterAddress, customData)
在深入 Paymaster 運作流程之前,我們先來回顧一下 4337 的鏈下流程。首先,使用者會將交易的操作和意圖放入 UserOperation 資料結構裡;接著,使用者可以將 UserOperation 交給任意的 Bundler,請他們協助交易上鏈;最後,Bundler 會統一將 UserOperation 送至鏈上全局共享的 EntryPoint 合約,由 EntryPoint 合約在鏈上執行 AA 的交易處理流程。
UserOperation 進到 EntryPoint 合約後,是否使用 Paymaster 會有不同的交易流程。讓我們先來了解一下沒有使用 Paymaster 的流程,即 UserOperation paymasterAndData
欄位為空(0x
)。
EntryPoint 呼叫 Account 合約(地址由 UserOperation sender
欄位指定) validateUserOp
方法,來確認 Account 是否授權執行這筆 UserOperation。
若 Account 授權同意,它必須自行支付這筆操作的 gas 費用給 EntryPoint。
EntryPoint 檢查收到足夠的 gas 費用後,會接續呼叫 Account 的 execute
方法來執行 UserOperation 的交易內容。
當 UserOperation paymasterAndData
不為空時(0x...
),EntryPoint 會改向 Paymaster 索取 gas 費用。
EntryPoint 呼叫 Account validateUserOp
來驗證 UserOperation 的合法性。
EntryPoint 會從 paymasterAndData
前 20 bytes 拿出 Paymaster 地址,呼叫 Paymaster validatePaymasterUserOp
來確認 Paymaster 是否願意為這筆 UserOperation 支付 gas 費用。
若 Paymaster 授權同意,Paymaster 會代替 Account 支付這筆操作的 gas 費用給 EntryPoint。
EntryPoint 檢查收到足夠的 gas 費用後,會接續呼叫 Account 的 execute
方法來執行 UserOperation 的交易內容。
EntryPoint 會呼叫 Paymaster postOp
方法,Paymaster 可以利用這個方法,在 Account 操作執行完成後,進行額外的後續動作。(例如:等 Account 完成 swap 操作後,要求以等值的 USDT 來償還 Paymaster 在第 3 步驟事先代墊的 gas 費用。)
4337 在鏈上將 Paymaster 抽象成一個通用的介面,只要有實作這個介面(如下圖所示),合約就可以被當成 Paymaster 來使用。
事實上,只有
validatePaymasterUserOp
是一定要實作的方法,如果不需要在 Account 操作結束後,執行額外的動作,可以完全略過postOp
的實作。(example)
實作這兩個方法時,有一些重要的事情必須注意:
validatePaymasterUserOp
不能存取任何會隨區塊變動的資訊,例如 block.number
、block.timestamp
。
只能存取與 Account 地址相關的 storage,詳細規則可以參考這裡。
若 Paymaster 需要進行下列動作時,需要事先在 EntryPoint 上進行 stake,stake 所需數量會由處理 UserOperation 的 Bundler 決定。(以 eth-infinitism/bundler 實作為例,stake 所需數量由啟動時的參數設定)
存取 Paymaster 自身合約的 storage 以及其他合約上與 Paymaster 地址相關的 storage,詳細規則可以參考這裡。
validatePaymasterUserOp
回傳非空的 context
,使 EntryPoint 觸發 postOp
函式;換句話說,也就是需要使用到 postOp
的功能時,就需要 stake。
postOp
沒有 validate 階段的限制,可以執行任何操作。
validatePaymasterUserOp
第一個回傳參數 context
必須不為空(context.length > 0
),否則 EntryPoint 會直接略過 postOp
。
validatePaymasterUserOp
和postOp
考量到安全性,建議只開放給 EntryPoint 呼叫,可參考官方合約 BasePaymaster 的實作。
在 validate 階段會有這麼多限制的原因,主要是為了保護 Bundler。Bundler 在收到 UserOperation 時會先做一次模擬,確保 UserOperation 上鏈後能順利執行;在模擬完成後,Bundler 會先將 UserOperation 放進 mempool,繼續收集其他 UserOperation;直到特定的條件達成後,例如 mempool 裡 UserOperation 的數量、距離上一次 bundle 發送的時間間隔等等,Bundler 才會將這段期間內收集的多筆 UserOperation,打包成一個 bundle,在同一筆交易裡送給 EntryPoint。
因此,UserOperation 被 Bundler 收錄,到實際上鏈之間,會有浮動的時間差存在,若 validate 階段允許存取會隨區塊變動的資訊,UserOperation 有可能在最初的模擬時驗證成功,卻在最後上鏈時驗證失敗,導致 Bundler 蒙受損失。
更多細節可以參考下週發布的 Bundler 文章。
在本小節最後,再多分享一些實作 Paymaster 時必須特別留心的事項給開發者們:
Paymaster 必須事先 deposit 足夠的 gas 費用到 EntryPoint 上,幫忙 Account 支付的 gas 費用會直接從 deposit balance 中扣除。
若 postOp
執行時發生 revert,EntryPoint 會將鏈上 state revert 回 validate 剛完成的時候,然後再重新呼叫一次 postOp
;若第二次 postOp
還是被 revert,則整個 bundle 會被 revert。
背後的構想是,Paymaster 在 validate 階段驗證 Account 擁有足夠的 token balance,但是 Account 有可能在 execute 階段將所有 token 轉移出去,造成 Paymaster 在 postOp
裡無法向 Account 索取預期的 token 數量而導致 revert。因此,EntryPoint 若能將 state revert 回 validate 階段剛驗證完 Account 擁有足夠的 token balance 時,就可以確保第二次 postOp
通過。
特別注意的是,在即將推出的 EntryPoint 0.7 版本中,這個機制會被移除,會帶來以下的改變:
EntryPoint 最多只會執行一次 postOp
,且 postOp
revert 不會導致整個 bundle revert,而是改為 emit event。
postOp
收不到款項的風險。在未來,Paymaster 透過 validatePaymasterUserOp
事先向 Account 收取費用,可能會是比較保險的做法。Paymaster postOp
實作邏輯變單純,不需要考慮到有可能被呼叫兩次的情況。
使用 Paymaster 時的 verificationGasLimit 可以降低,預留的乘數從 3 倍降為 2 倍,減少 prefund gas 的數目。
Bundler 會有特定的演算法,來計算 Paymaster 的評分(reputation)。
在這一小節裡,我會整理出近期市場上已經在使用的幾種 Paymaster 實作範例,開發者在設計 Paymaster 時可以當作參考。我大致上將這些實作分成四種類別,為了方便討論,我簡略地為這些類別取了個名字,但是這些名字還不是業內共識,使用上請多加留意。
Permissioned Paymaster 設計原理很單純,Paymaster 合約身上可以事先記錄特定一組 admin 地址,只要 UserOperation paymasterAndData
裡有包含 admin 對這筆 UserOperation hash 的簽名,就會為這筆 UserOperation 支付 gas 費用。
這類型的 Paymaster 服務如 Pimlico Verifying Paymaster(doc),需要在鏈下串接服務商的 API,只要能從鏈下後端服務取得 admin 的授權簽名,鏈上 Paymaster 合約就會贊助使用者 UserOperation 的 gas 費用。
validateUserOp
paymasterAndData
中是否包含 admin 簽名。postOp
完整應用流程如下:
服務商設定一組 admin 地址到 Paymaster 合約上。
使用者透過服務商鏈下的 Paymaster Service,獲取 paymasterAndData
,其中會包含 admin 對 UserOperation hash 的簽名。
使用者將 paymasterAndData
放入 UserOperation,送給 Bundler。
validatePaymasterUserOp
檢查 paymasterAndData
裡是否包含 admin 對 UserOperation hash 的簽名。
若有,則為該筆操作支付 gas 費用。
若無,則拒絕支付。
儘管取得 admin 簽名後,鏈上 Paymaster 合約會直接贊助 UserOperation 並不額外收取費用,但是服務商的鏈下後端服務,可以設計額外的機制,例如使用者必須事先儲值足夠的金額,後端服務才會給你 admin 的授權簽名。
依照業務需求,Permissioned Paymaster 也可以在合約裡向 Account 收取服務費用。
Accounting Paymaster 會在合約上記錄每個使用者的餘額,使用者在運用 Paymaster 服務前,需要事先儲值足夠的金額至 Paymaster 合約,當 Paymaster 為使用者的 UserOperation 預付以原生代幣計價的 gas 費用後,會再從使用者儲值在合約上的餘額,取回服務的費用。
這裡提到的餘額,不一定要是 ETH,可以根據業務需求,要求使用者儲值不同的幣種到合約上。
這類型的 Paymaster 服務如 Biconomy Paymaster(doc),需要在鏈下透過他們的 Dashboard 來事先儲值 ETH 至 Paymaster 合約上;除此之外,Biconomy Paymaster 同時也套用了 Permissioned Paymaster 的設計模式,需要特定的 admin 簽名才可以使用他們鏈上的 Paymaster 服務。
validateUserOp
驗證 paymasterAndData
中是否包含 admin 簽名。
驗證使用者在合約上是否有足夠的儲值金。
postOp
完整應用流程如下:
服務商設定一組 admin 地址到 Paymaster 合約上。
使用者透過服務商鏈下 Paymaster Service,儲值金額至 Paymaster 合約。
使用者透過服務商鏈下的 Paymaster Service,獲取 paymasterAndData
,其中會包含 admin 對 UserOperation hash 的簽名。
使用者將 paymasterAndData
放入 UserOperation,送給 Bundler。
validatePaymasterUserOp
檢查 paymasterAndData
裡是否包含 admin 對 UserOperation hash 的簽名,並且檢查使用者在合約上是否有足夠的儲值金。
postOp
從使用者在合約上的儲值金,取回代墊的 gas 費用。
Accounting Paymaster 有幾種應用情境,例如 DApp 項目方可以預先儲值足夠的金額至 Paymaster 合約上,來獎勵 DApp 使用者可以進行免手續費的操作;或是在合約上接受儲值不同的幣種,來達成以其他幣種支付 gas 費用的效果。
Off-chain Oracle Paymaster 會依據鏈下的報價資訊,讓使用者能透過不同的 token 來支付 gas 費用。這個設計需要仰賴一組特定的 oracle owner 地址,來讓 Paymaster 合約確保報價來源的合法性;除此之外,使用者必須事先授權 token allowance 給 Paymaster 合約,Paymaster 合約才能在交易的最後從 Account 身上轉回與代墊 gas 費用等值的 token。
這類型的 Paymaster 服務如 Stackup Paymaster(doc)、Candide Paymaster,服務商需要在鏈下提供額外的報價服務。只要兌換率是服務商可以接受的,誰來使用 Paymaster 服務差異並不大,因此相較於 Permissioned Paymaster,Off-chain Oracle Paymaster 是更開放的設計模式。
validateUserOp
驗證 paymasterAndData
中的報價資訊,是否經過 oracle owner 簽名授權。
根據鏈下報價資訊,驗證 Account 是否有足夠的 token 支付 gas 費用。
postOp
根據鏈下報價資訊,計算與 gas 費用等值的 token 數量。
從 Account 身上取回該數量的 token。
由於 validate 階段有諸多的限制,例如無法存取鏈上 Oracle 的 storage,因此才需要從鏈下提供報價。
完整應用流程如下:
服務商設定一組 oracle owner 地址到 Paymaster 合約上。
使用者透過服務商鏈下的 Paymaster Service,獲取 paymasterAndData
,其中會包含報價資訊以及 oracle owner 對報價的簽名。
使用者將 paymasterAndData
放入 UserOperation,送給 Bundler。
validatePaymasterUserOp
檢查 paymasterAndData
裡的報價資訊,是否經過 oracle owner 授權簽名,並且依據報價資訊,檢查 Account 是否有足夠的 token 支付 gas 費用。
postOp
根據鏈下報價資訊,計算與 gas 費用等值的 token 數量,向 Account 索取。
On-chain Oracle Paymaster 會根據鏈上的報價資訊,讓使用者能透過不同的 token 來支付 gas 費用。這種設計模式完全不需仰賴任何鏈下服務,實現了無准入的去中心化理想,但是換來的是 Paymaster 合約實作的複雜性。
先前有提到因為 validate 階段的限制,導致 Paymaster 不能存取到鏈上 Oracle 的 storage,所以無法在鏈上取得報價。但是 On-chain Oracle Paymaster 這個設計模式巧妙地繞過了這個限制,透過在 Paymaster 合約上緩存過去的報價資訊, validatePaymasterUserOp
只需要存取到 Paymaster 合約本身的 storage,就可以取得參考的報價,並且利用沒有 storage 存取限制的 postOp
,去向鏈上 Oracle 更新這份報價的緩存,來計算使用者最後需要支付的 token 數量。
這類型的 Paymaster 服務如 Pimlico ERC20 Paymaster,只需要部署 Paymaster 合約就可以提供服務。但是對服務商來說,還是需要不定時到 EntryPoint 上補充 Paymaster 合約的餘額,並且定期檢查合約上緩存的報價,是否已經偏離市場太多,必要時需要以手動的方式來更新緩存的報價。
validateUserOp
透過合約內過去的緩存報價,驗證 Account 是否有足夠的 token(視情況加上額外的 buffer)支付 gas 費用。
提前向 Account 索取 token。
由於 validate 階段使用的報價為過去的緩存,為了避免短時間內價格大幅波動,所以收取了額外的 buffer 以減少 Paymaster 虧損的可能性。
postOp
向鏈上 Oracle 取得最新的報價資訊,更新合約上的緩存。
根據最新的報價資訊,計算 Account 實際需要支付的 token 數量。
若在 validate 階段有超額收取,則退回多出來的部份給 Account。
完整應用流程如下:
使用者根據 Paymaster 合約介面,自行組建 paymasterAndData
放入 UserOperation,送給 Bundler。
validatePaymasterUserOp
根據緩存的報價,加上額外的 buffer 避免價格波動的風險,驗證 Account 身上是否有足夠的 token 支付 gas 費用,提前向 Account 索取 token。
postOp
向鏈上 Oracle 取得最新報價,更新 Paymaster 合約上的緩存,並計算 Account 實際需要支付的 token 數量。
目前 4337 最被廣泛使用在 Polygon 上,因此接下來的市場數據會以 Polygon 為主。
數據與圖表來源:ERC-4337 Smart Accounts
截至 2023/12 月,Paymaster 市佔前三名分別是 Plimlico、Biconomy 和 Alchemy,其中 Pimlico 為先前介紹的 Pimlico Verifying Paymaster(Permissioned Paymaster),Biconomy 為先前介紹的 Biconomy Paymaster(Accounting Paymaster)。比較會令開發者感到意外的是,去中心化的 Pimlico ERC20 Paymaster(On-chain Oracle Paymaster) 方案事實上沒有多少人在使用,合理的猜測可能是各家廠商的產品,可以透過中心化授權的方案,直接給予使用者交易費上的優惠。
雖然原始數據中,無法得知 UserOperation 使用 Paymaster 的比例,但是從上面兩張圖表可以觀察到,UserOperation 數量與 Paymaster 贊助的 gas 量大致上呈現正相關,藉此可以推測出有一定比例的 UserOperation 持續在使用 Paymaster。
GitHub - eth-infinitism/account-abstraction at abff2aca61a8f0934e533d0d352978055fddbd96
GitHub - consenlabs/ethtaipei2023-aa-workshop: Account abstraction workshop @ ETHTaipei 2023
2023 Dapp Learning AA Introduction by imToken
Paymaster 投影片:DApp Learning Paymaster