From 50725399475e8cf74c4429dfa26e35915c75919b Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Wed, 18 Mar 2026 17:39:25 -0400 Subject: [PATCH] feat: add "vanilla swap" hooklist This commit adds the vanilla swap hooklist for hooks that dont affect the swap behavior at all. these should be trivial for routers to integrate since they work just like standard v4 pools --- .github/workflows/regenerate.yml | 2 +- hooklist-vanilla-swap.json | 101 ++++++++++++++++++ scripts/__pycache__/aggregate.cpython-312.pyc | Bin 3378 -> 4583 bytes ...est_aggregate.cpython-312-pytest-8.3.5.pyc | Bin 11296 -> 14738 bytes scripts/aggregate.py | 15 +++ scripts/test_aggregate.py | 25 ++++- 6 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 hooklist-vanilla-swap.json diff --git a/.github/workflows/regenerate.yml b/.github/workflows/regenerate.yml index ad36a5a4..5d0fccd9 100644 --- a/.github/workflows/regenerate.yml +++ b/.github/workflows/regenerate.yml @@ -44,6 +44,6 @@ jobs: git config user.name "hook-registry-bot[bot]" git config user.email "hook-registry-bot[bot]@users.noreply.github.com" git remote set-url origin "https://x-access-token:${APP_TOKEN}@github.com/${{ github.repository }}.git" - git add hooklist.json + git add hooklist.json hooklist-vanilla-swap.json git diff --staged --quiet || git commit -m "chore: regenerate hooklist.json" git push diff --git a/hooklist-vanilla-swap.json b/hooklist-vanilla-swap.json new file mode 100644 index 00000000..e9c05ced --- /dev/null +++ b/hooklist-vanilla-swap.json @@ -0,0 +1,101 @@ +[ + { + "hook": { + "address": "0xc5a48b447f01e9ce3ede71e4c1c2038c38bd9000", + "chain": "base", + "chainId": 8453, + "name": "Graduation Hook (Base)", + "description": "Restricts pool initialization to a designated Moxie bonding curve address, ensuring Uniswap v4 pools can only be initialized through the authorized bonding curve as part of a token graduation process.", + "deployer": "", + "verifiedSource": true, + "auditUrl": "" + }, + "flags": { + "beforeInitialize": false, + "afterInitialize": true, + "beforeAddLiquidity": false, + "afterAddLiquidity": false, + "beforeRemoveLiquidity": false, + "afterRemoveLiquidity": false, + "beforeSwap": false, + "afterSwap": false, + "beforeDonate": false, + "afterDonate": false, + "beforeSwapReturnsDelta": false, + "afterSwapReturnsDelta": false, + "afterAddLiquidityReturnsDelta": false, + "afterRemoveLiquidityReturnsDelta": false + }, + "properties": { + "dynamicFee": false, + "upgradeable": false, + "requiresCustomSwapData": false + } + }, + { + "hook": { + "address": "0xd3c1f2174f37f88811f99b1b1b4c1356c0246000", + "chain": "base", + "chainId": 8453, + "name": "Aquinas Hook (Base)", + "description": "Restricts Uniswap v4 pool initialization to factory-authorized Safe addresses for Aquinas DAOs, preventing frontrunning of LP initialization by requiring the caller to be pre-approved by the Aquinas factory.", + "deployer": "", + "verifiedSource": true, + "auditUrl": "" + }, + "flags": { + "beforeInitialize": true, + "afterInitialize": false, + "beforeAddLiquidity": false, + "afterAddLiquidity": false, + "beforeRemoveLiquidity": false, + "afterRemoveLiquidity": false, + "beforeSwap": false, + "afterSwap": false, + "beforeDonate": false, + "afterDonate": false, + "beforeSwapReturnsDelta": false, + "afterSwapReturnsDelta": false, + "afterAddLiquidityReturnsDelta": false, + "afterRemoveLiquidityReturnsDelta": false + }, + "properties": { + "dynamicFee": false, + "upgradeable": false, + "requiresCustomSwapData": false + } + }, + { + "hook": { + "address": "0xde400595199e6dae55a1bcb742b3eb249af00800", + "chain": "ethereum", + "chainId": 1, + "name": "M0 Tick Range Hook", + "description": "Restricts liquidity provision to a configurable tick range by reverting in beforeAddLiquidity if the position's ticks fall outside the allowed bounds. An authorized manager role can update the tick range.", + "deployer": "", + "verifiedSource": true, + "auditUrl": "" + }, + "flags": { + "beforeInitialize": false, + "afterInitialize": false, + "beforeAddLiquidity": true, + "afterAddLiquidity": false, + "beforeRemoveLiquidity": false, + "afterRemoveLiquidity": false, + "beforeSwap": false, + "afterSwap": false, + "beforeDonate": false, + "afterDonate": false, + "beforeSwapReturnsDelta": false, + "afterSwapReturnsDelta": false, + "afterAddLiquidityReturnsDelta": false, + "afterRemoveLiquidityReturnsDelta": false + }, + "properties": { + "dynamicFee": false, + "upgradeable": false, + "requiresCustomSwapData": false + } + } +] diff --git a/scripts/__pycache__/aggregate.cpython-312.pyc b/scripts/__pycache__/aggregate.cpython-312.pyc index ce127620c5492902c514da42b9183195e3a5a4e2..2094beb61c4d17a4af6cd6d8be20ba1922db82c8 100644 GIT binary patch delta 1625 zcma)6TTC2P7(Qn%%w;cFxoiu?on?V#C~R74gH2WvC}k^)RZBt(v0Vpd7M2dXWM(L3 zU9u!%fJ(xyqk^d#Vl;tZld3N^#wTicFg9zUN!<{mzUU*dCbT?xW|oCD^}(|_-~4;d z_y7NQ{&V@N><_p72b+xrsQJpj4ZlOD?1$mIDNiG`rg|$|ds&f|Xo)(4=0!4Rg$R+E zB6=+%I|X{Jl1;Q+tpz}#F(6r2jZmmTFpiC5yzR?R)(R3lkzxC!P*jn+#)Od!BZM?b zG0oD0;R}*BszlT_N!El+$v!22SgA&l`=Oa23l#d`s6XmMFbx+#9JGOXI1T%;0nm?K zt^nW$OlbQqGcW{2I7Mq%)*m2xPV(v=OM!DoXJHDPa}p@T1nfcxO=}GBSnO0*5jPl( zUR615P|&zR;hMxnqMV9W*PuwUph+SZl7#`4lOjT2LY74DzhoPdPa3kl2;PSQR4zmM zWh(#^a26ARQSdl4@B_F2A(*ohU>x4U#~_boNHg^L6~N;e%zIqn5lyipP>r}8H>nh& zZ-(&=ND=A!h>~cI)r7(_s(J955DCk&5Hyr}eR8y4kkw{yj@XR05vXMd9{&j*PlB|k z<4)`w*D_r8tgyFF+_K$?Jy8iX{noQ3h7eqcjAz)ci!HAP+dEtQT|8#)P*#gkVeSn> zN)N~s{YQS~1;nU9T{Y68biX-{ZdwAV(WL9MGaH_c?_vlZRCU~FNsj(x38aa@llzu{ zN+a@gMLqF3QBUXsRZ9MXR=uGAN^$xGs!@5=Ac&Jgj;x)J zBCW46RW)&HKM|c5n0PvHg8sjOa}gmB&0kI;1X_7cg2-s-CS==zhMPcSKgJLI^Z1s- zc@A`rf*hv#`9=hCLPu$7KjnQ<@Vu&OoeowWwztm#+S*47x) zZk|GPIbtWd{OP^R6pm<7(|pR`JETSl!u~D1`xDoJ-zRT7#8S7p=-DN3e zGF0v#EGRxi3=83iat@J?p{{mA{gOp4*(xeoXi7CL_)@+lCVkAmQFLyyQ@?BJNfcHt zRd3j9C;eNDZAM-^v&nEvEnBp8o|$Emp2ZVOuRWkW4{2tenx&G&oNb2$tm|(YknBw0 zBYg4fO*R=%yM1Z)#Z97li(qD6dar-cqsz%FX?J&;z4(B*wBrP9d73HP-C+S+nPyz3 z;Yu^yuG&4${w2>u%CtPi5#}&{$e9$iwrz zT_3TY_BA12^J1CnH5d_LSPBLe=NA@(LO=UOshPe`xic-98TN zufs`py8wfoa8X-o`!Kc*40~C;{rt+%Bh&tiErmUByAcr9nb>{&$j=1*cKbFCh%z(B EKbBLANdN!< delta 503 zcmYL_JxIe)6ov15`G0NFR;5*oR$GfkL`%^@@CS#EI=JZ)im6a-DkSXs@09Z1H5@~r+HVBUJ5!T7geOts^KwYU1KjOPjXVz=kA3%$d3Vn-gP&?YQ^1?55dVwTeWw6^J zK+>)<+O_*xvaQ4mgljKP7UJ9!5emK1Q(P#dq*vK6yp@KbqcuHDzoYXr-gb}Iiq(=~ oT!Iy2z&|f9RQJsNN@>YjVa)q|d!25_7NnnC1TBA!Q8$+O1=)*bF8}}l diff --git a/scripts/__pycache__/test_aggregate.cpython-312-pytest-8.3.5.pyc b/scripts/__pycache__/test_aggregate.cpython-312-pytest-8.3.5.pyc index 8e6b969e3923362e56951ca24c4f9322217579d0..17a5b20378547ab031203b04af32fc5f77c67a48 100644 GIT binary patch delta 3764 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 F`3G{7HyQu{ diff --git a/scripts/aggregate.py b/scripts/aggregate.py index 34c6d5aa..990ec260 100644 --- a/scripts/aggregate.py +++ b/scripts/aggregate.py @@ -32,11 +32,20 @@ def aggregate_hooks(hooks_dir: str, schema: dict | None = None) -> list[dict]: return hooks +SWAP_FLAGS = ("beforeSwap", "afterSwap", "beforeSwapReturnsDelta", "afterSwapReturnsDelta") + + +def filter_vanilla_swap(hooks: list[dict]) -> list[dict]: + """Return hooks that have no swap-related flags enabled.""" + return [h for h in hooks if not any(h["flags"][f] for f in SWAP_FLAGS)] + + def main(): repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) hooks_dir = os.path.join(repo_root, "hooks") schema_path = os.path.join(repo_root, "schema.json") hooklist_path = os.path.join(repo_root, "hooklist.json") + vanilla_path = os.path.join(repo_root, "hooklist-vanilla-swap.json") with open(schema_path) as f: schema = json.load(f) @@ -47,7 +56,13 @@ def main(): json.dump(hooks, f, indent=2) f.write("\n") + vanilla = filter_vanilla_swap(hooks) + with open(vanilla_path, "w") as f: + json.dump(vanilla, f, indent=2) + f.write("\n") + print(f"Aggregated {len(hooks)} hooks into hooklist.json") + print(f"Filtered {len(vanilla)} vanilla-swap hooks into hooklist-vanilla-swap.json") if __name__ == "__main__": diff --git a/scripts/test_aggregate.py b/scripts/test_aggregate.py index f5a4b4fb..65a98376 100644 --- a/scripts/test_aggregate.py +++ b/scripts/test_aggregate.py @@ -2,7 +2,7 @@ import os import tempfile import pytest -from aggregate import aggregate_hooks +from aggregate import aggregate_hooks, filter_vanilla_swap SCHEMA = { @@ -172,6 +172,29 @@ def test_aggregate_with_schema_missing_flag(): aggregate_hooks(tmpdir, schema=SCHEMA) +def test_filter_vanilla_swap_excludes_swap_hooks(): + swap_hook = _valid_hook() # has beforeSwap=True + vanilla_hook = _valid_hook(address="0x00000000000000000000000000000000000000A0") + vanilla_hook["flags"]["beforeSwap"] = False + vanilla_hook["flags"]["beforeInitialize"] = False + result = filter_vanilla_swap([swap_hook, vanilla_hook]) + assert len(result) == 1 + assert result[0]["hook"]["address"] == vanilla_hook["hook"]["address"] + + +def test_filter_vanilla_swap_excludes_returns_delta(): + hook = _valid_hook() + hook["flags"]["beforeSwap"] = False + hook["flags"]["afterSwap"] = False + hook["flags"]["afterSwapReturnsDelta"] = True + result = filter_vanilla_swap([hook]) + assert result == [] + + +def test_filter_vanilla_swap_empty(): + assert filter_vanilla_swap([]) == [] + + def test_aggregate_with_schema_wrong_type(): with tempfile.TemporaryDirectory() as tmpdir: hook = _valid_hook()