
開發者筆記:Aggregator Hook 的誕生
TechFlow Selected深潮精選

開發者筆記:Aggregator Hook 的誕生
本文將探討 Aggregator Hook 的起源、操作機制,以及它為 Uniswap 及更廣泛的 DEX 生態系統帶來的深遠影響。
撰文:Attens & Bruce
編輯:Lisa,DODO Research
這(Aggregator Hook)對其他 DEX 是一件很好的事,增加一段代碼就可以多接入一個「超級聚合器」,何樂而不為;對 Uniswap 當然也是一件很好的事,它可以絲滑地、無成本地吸納其他 DEX 的流動性,為己所用。
— Attens, DODO DEV Leader
Uniswap V4 發佈後迅速在開發者中掀起了一場風暴:Hook 結構為傳統的、一成不變的模板化 pool 合約帶來了豐富的可拓展性。Uniswap V4 Hook 的接口幾乎涵蓋了池子 - 資金交互的全生命週期,為開發者的「頭腦風暴」提供了豐饒的土壤。從相對而言比較簡單的 TWAMM Hook,引入時間加權預言機的保護;到 LimitOrder Hook 展示 Hook 更強大的潛力,又或是在黑客松裡誕生的跨鏈交易 Hook,各種 Hook 如雨後春筍蓬勃生長。
身為 Defi 從業者,我們自然也不會坐壁上觀。在 11 月舉行的 ETH Global 中,我們提出了一種新的 Hook 設計,並有幸獲得 “Best of Use Hook” 獎項。由於其具有較高的泛用性,可以作為一個代碼組件搭載在各種流動性池子合約中,我們將其命名為:Aggregator Hook。
Aggregator Hook 旨在充當 “橋樑”,使得市場上的流動性能直接鏈接到 Uniswap V4 的池子;同時利用 “及時原則” 動態管理流動性資金,在交易前注入流動性,在交易後撤出流動性,將流動性遷移對原池子的影響最小化。這種集成使 LPs 能夜以前所未有的便捷方式管理資金,利用 Uniswap 的堅固架構,同時利用更廣泛的 DEX 生態系統中的流動性。
本文將探討 Aggregator Hook 的起源、操作機制,以及它為 Uniswap 及更廣泛的 DEX 生態系統帶來的深遠影響。
I. Aggregator Hook 的誕生
最初這個想法來源於我們的一個樸素需求:我們有一個報價效率很高的 DODO V3 池,能不能將這一套系統利用 Hook 遷移到 Uni V4 上?
在調研 LimitOrderHook 時我們得到了啟發:Hook 可以託管一部分資金。這是一個不起眼的發現,但它打開了兔子洞的大門。“Hook 是個獨立合約” 加上 “Hook 可以託管資金”,是否我們可以推導出:Hook 本身就可以是一個池子?以及,現在我們有一個合約,這個合約可能是其他 DEX 的池子,或者本身有其他邏輯(例如,可能是一個眾籌池子,也可能是一個交易託管系統),但同時這個合約滿足 Uni V4 對 Hook 合約的要求,且有 Uni V4 Hook 對應的函數,那麼它同時也可以是一個 Hook。
於是得到了初始構想:Aggregator Hook 是一個池子本位的 Hook,既是一個 DODO V3 池子,也能作為 Uni V4 的 Hook,我們要把報價系統遷移到 Hook 對應的函數里,利用 JIT 使得用戶在 Uni V4 交易得到和 DODO V3 相同的報價。初始的方案非常自然:我們有每個幣的價格上限、價格下限、代售數量,將價格上下限轉換成 tick,按照代售數量填充流動性,用戶交易,交易成功,完美——等等!我們遇到了巨大的價差。
我們嘗試了許多種 tickSpacing 和 Fee 的組合,也應用過各種價格上下限對應的 tick 組合,最好的結果也與原生 DODO V3 的報價有 0.2% 的價差,足以影響用戶的交易體驗。很明顯,這是由於 DODO V3 與 Uni V4 的算法不同導致的,為了解決這個價差,面前有兩個門:
-
正門:充分考慮 Uni V4 和 DODO V3 的算法差異,找到流動性 remapping 公式
-
後門:利用 fromAmount 從 DODO V3 得到一個價格,利用這個價格直接確定流動性填充參數,以消除價差
我們經過了短暫嘗試後,毫不猶豫地選擇了走後門!雖然我們相信第一個方案如果真能推出解析解的話應該可以產出一篇優雅的小論文,可惜我們不是數學家。第二個方案的弊端顯而易見:一定會引入更多的 gas 消耗,畢竟增加了一次詢價;但優勢同樣也非常明顯:利用 fromAmount 得到價格的函數並不是 DODO V3 獨有的,所有的 Dex pools 都會有類似的詢價函數,getAmountsOut、get_dy、querySellTokens… 不管怎樣的函數名,總有方法從合約得到詢價結果。則這個 Hook 組件不僅可以在 DODO V3 池子裡使用,也可以在任意 Dex 池子裡集成使用,甚至於也不一定是一個池子,而是一個 solver 的報價組件。任何一個有流動性、對該流動性有報價的合約,只要增加這段 Hook 代碼,就可以在不影響原獨立邏輯的情況下同時成為一個 Hook,以及它可以通過 Hook - Pool 同時構成一個合法的 Uni V4 池子。這時,理想情況下,Uni V4 的路由不僅可以路由到傳統的 Uni V4 池子,同時也可以路由到我們創造的這種 trick pool。
我們感到興奮 - 這對其他 Dex 是一件很好的事,增加一段代碼就可以多接入一個 ”超級聚合器“,何樂而不為。對 Uniswap 當然也是一件很好的事:它可以絲滑地、無成本地吸納其他 Dex 的流動性,為己所用。
當然,這種宏偉的願景取決於 Uni V4 最後會使用怎樣的路由算法、又怎樣處理這些池子的優先級。這是另一個話題,也許我們可以寫另一篇文章討論 Uni V4 的路由算法。但由於 Hook 的特殊性,可以肯定的是不管怎樣的路由算法,必須考慮到 Hook 的影響,因為 Hook 裡的操作直接和用戶能得到的最終報價有關。
希望大家此時還記得我們宏偉假設的最基本前提:我們得到一個報價,我們找到一個流動性填充方式可以在 Uni V4 中投影這個報價,使得用戶通過 Uni V4 交易與通過 Hook-Pool 直接交易拿到的價格相同。
最終,我們做到了。這就是為什麼我們正式稱它為 “Aggregator Hook”。
II. Aggregator Hook 的操作流程
而當我們真正談論到 Aggregator Hook 的具體實現,你會發現魔法其實只是表演者袖子裡那枚不易被察覺的硬幣:
-
用戶發起 swap call
-
觸發 beforeSwapHook,Hook 中執行三個操作:
-
移除池子中剩餘的全部流動性(Hush! It’s the key point.)
-
利用用戶的 fromAmount 得到 Hook-Pool 原生的交易價格
-
通過交易價格計算 modifyPosition 的參數,填充流動性
-
swap call 結束
-
外層 Router 處理用戶 transferIn 及 transferOut
如下圖所示:

當用戶的交易訂單接近時,BeforeSwap 鉤子被激活,流動性提供者(LPs)向 Uniswap V4 池注入流動性。增加的流動性適應了即將到來的訂單,促使其成功執行。然而,與其在交易結束時撤回這些流動性,不如讓它留在池中。這構成了第一筆交易完整生命週期的一部分。
創新之處在於處理後續交易。在下一筆交易啟動前,BeforeSwap 鉤子再次被觸發。它的首要任務是移除上一筆交易遺留的任何多餘流動性。
一旦殘餘流動性被撤回,LPs 重複初始步驟:他們分析新交易的需求,並在兩個價格區間內添加精確數量的流動性。這種細緻的添加確保了流動性不僅被最優化利用,而且還以最高效率定位,從而減少滑點並改善 Uniswap 生態系統內的整體交易執行。
III. 機制和代碼剖析
最精彩的部分應當壓軸。
When to remove liquidity
首先我們要解釋一下為什麼我們需要移除流動性,這基於以下兩點考慮:
-
在該 Hook 中,Hook-Pool 才是主體,流動性應當儘可能地集中在 Hook-Pool 中。
-
池子沒有多餘流動性時,進行價格 remapping 的計算比較簡單。
In JIT,通常的做法是:在用戶 swap 前充入流動性,在用戶 swap 之後提出流動性。我們一開始當然也是這樣想的,beforeSwapHook 中添加流動性,afterSwapHook 中移除流動性,邏輯很順暢——然而 Hook 總是驚喜多多。讓我們看一下目前的 Uni V4 最新的 testRouter 結構:



See, 阻礙我們的偉大計劃的核心就是:用戶在 manager.swap 完成之後才會和 poolManager 進行真正的代幣交換。這意味著:afterSwapHook 並不能真正在 'after swap' 後發生,我們就無法在 afterSwapHook 裡處理這筆流動性移除。removeLiquidity 函數失去了它的最佳站位,我們因此手忙腳亂,任由這位被放棄的演員只能乾巴巴地在測試裡站在 swap 函數調用後面,單獨一行,突兀地像個小丑。走投無路的我們正打算利用 swap 函數里的 hookData 做文章,思考改造 testRouter 的可能性;這時,金蘋果製造者之一,Uni 的 @ken 和他們的 Dev @saucepoint 給了我們至關重要的建議:可以在下一筆交易的開始移除流動性。
這個方案對於我們一定是最佳的。首先,它對代碼的改動極小,當時我們離黑客松 presentation 只剩 7 小時了;其次,它不再依賴陰晴不定的 testRouter 完成自動化的流動性移除,你只需要忍耐一小會,等到下一個用戶進來,上一筆的應收款就會原樣退還。
當然 Hook-Pool 的 owner 可以在任意時刻提出 Uni V4 中的流動性,並不一定要等到下一個交易用戶,以下 remove 函數是 public 的:


同時,為了保護資金,我們使用了 beforeModifyPosition Hook 驗證修改流動性的 msg.sender

How to fill liquidity
Uni V4 和 Uni V3 計算公式是一致的,儘管 Uni V4 的 pool 變成了一個 lib。我們僅考慮在一個極小的 tick 間隔內填充單邊流動性,例如 tick -46874 與 tick -46873 之間填充流動性,以儘量減少跨 tick 消耗。閱讀代碼和相關的解析文章可以利用 Uni 的算法得到以下公式。

對應代碼:

但這不是結束。觀察 Uni V3/V4 的流動性分佈我們可以看到:

填充流動性之後,該段價格實際只有 50% 的價格區間能滿足我們的單邊交易需求。如果我們 sell token0 to token1,選定的填充 tick 區間為 [tickLower, tickUpper],對應的價格區間為 [priceLower, priceUpper]。在交易進行中,預期的價格行為是從 price 降低,降低到目標價格,但該目標價格不能低於 (priceLower + priceUpper) / 2。如果低於 (priceLower + priceUpper) / 2,會表現為 price_next 超過價格下限,需要對 tick 下限做校正。對於另一端的交易也需要校正。舉個例子:

該校正對應代碼:

Price Balance
到目前為止,我們吹過的牛基本都圓回來了。Aggregator Hook is good, but not flawless. 最核心的限制就是交易方向。這個限制描述為以下情況:
-
當前 V4 池子價格在 tick 0 對應的價格上,記為 currentPrice
-
當 targetPrice > currentPrice 時,V4 池僅能執行 swap 1 to 0
-
當 targetPrice < currentPrice 時,V4 池僅能執行 swap 0 to 1
這個限制其實也有非常符合直覺的理解方法。我們可以將 targetPrice 看成市場價格,currentPrice 是老 Dex 池通過交易產生的價格。當 Dex pool 價格比市場價格低時,池子要求用戶只能買入 token0 以提高價格,達到價格平衡;而 Dex pool 價格比市場價格高時,池子要求用戶只能買入 token1 以降低價格,達到價格平衡。
該限制條件代碼表示為:

IV. 深入討論與展望
我們認為 Aggregator Hook 最大的意義在於更新了設計 Hook 的角度。
之前我們考慮 Hook 設計時一直沿用 Uni V4 Pool 本位的角度,思考使用 Hook 能給 “交易” 帶來什麼新的功能;Aggregator Hook 給出了一種全新的角度。由於 Hook 的獨立性,這個合約並不一定 “All in Hook”,Hook 可能只是它的一部分功能——製造與 Uni V4 池子的橋樑,但它本身可以是執行任何操作的合約,“交易” 與 Uni V4 是這個合約功能的一部分。
舉一個很簡單的例子,一些小項目方會聲稱自己眾籌代幣後會將一定比例的代幣與資金添加到 UniSwap 中。在 Uni V3 的時代,眾籌合約和 Uni V3 合約的操作是異步的,用戶們只能依賴項目方進行資金轉移,但有了 Uni V4 Hook 之後:我們為什麼不利用 Hook 的獨立性把添加流動性的操作直接集成在同一個合約裡?甚至在用戶充值時就直接執行流動性添加操作(當然這會帶來很高的 gas 消耗,但如果有用戶需要這種透明性的話)。這很瘋狂,但它預示了 Hook 的無限可能。
Aggregator Hook 最大的限制其實是其交易的 gas 消耗。正常 Uni V3 的 gas 消耗大約在 12w 左右,而由於我們在其中進行了各種流動性增減操作,單次的交易 gas 用 forge snapshot 計算在 42~55w 左右,遠高於正常的 swap 行為,註定了這種集成池子的辦法只能在 storage gas 不敏感的 L2 上使用,或者在 gas price 很低的鏈上使用。Trick 付出 trick 的代價,這很合理。
它當然還有一些可以提升的地方,集中在流動性充提的部分。有兩個可能的方向,一個簡單一點,一個複雜一點。先來點小菜:
-
在 Price Balance 小節中我們討論了交易方向問題,如果我們保持流動性移除的方案,targetPrice 和 currentPrice 的規則無法更改,但是 tick0 和 limitTick 之間卻並不一定不能相等。只是當 limitTick = tick0 時,充入流動性時會變成雙邊充值,計算充入流動性數額的公式需要更改。這個更改可以將交易方向的要求稍微放寬一點。放寬一個 tick。
更復雜的:
-
既然我們已經考慮到雙邊充值的流動性了,為什麼不貫徹到底,不再強制移除流動性?而僅在 Hook-Pool 管理者認為需要的時候 removeLiquidity。進一步減少 user swap 時 gas 消耗,使 Aggregator Hook 更 ”可用“。當然這很有可能會引入比上小菜方案更復雜的流動性充值算法。
當前 Aggregator Hook 池子的源碼如下:
https://github.com/Attens1423/Aggregator-Hook
為了直觀看到交易結果我們增加了 afterSwap Hook 的調用打印結果,但這不是必須的。內部有許多中間值打印,可以使用 forge test 觀看 Aggregator Hook 的表演。
DODO V3 也許會成為第一個吃螃蟹的池子。我們下一步的計劃是打算將 Aggregator Hook 集成到 DODO V3 池子中,這裡應該還有一些必要的 pool reserve 和 pool state 的更新工作,在完成了所有 test 之後我們會將它開源,實現最初的 Hook-Pool 設想。
歡迎加入深潮 TechFlow 官方社群
Telegram 訂閱群:https://t.me/TechFlowDaily
Twitter 官方帳號:https://x.com/TechFlowPost
Twitter 英文帳號:https://x.com/BlockFlow_News














