Skip to content

Hook allowlist admits by address, not by code: consider pinning the code hash #1409

Description

@blazephoenixxyz-crypto

Summary

lib/util/hooksAddressesAllowlist.ts admits hooks by address. Around 180 addresses are listed across chains, and each entry is a decision that the code at that address was reviewed and found acceptable to route user trades through.

The gap is that an address is not code. A hook deployed behind a proxy can be reviewed as benign and later point at a different implementation, and the allowlist entry keeps passing because the address never changed. Whatever review preceded admission then applies to a program that is no longer the one executing.

This is not hypothetical for hooks specifically: a hook is the one contract in the v4 swap path that can be given permission to alter the accounting, so it is also the contract where a silent implementation swap is worth the most to an attacker.

Suggested change

Record the code hash alongside the address at review time, and verify it before routing.

Because routing-api is off-chain, this is cheap in a way the on-chain version is not: one eth_getCode (or a keccak256 of it) per admitted hook, cached, refreshed on whatever cadence you already use for chain state. A mismatch means the reviewed code is not the deployed code, and the safe response is to drop that hook from the route set and alert, rather than to route through it.

Concretely, the allowlist entries would carry a pair rather than a bare address:

{ address: '0x...', codeHash: '0x...' }  // codeHash recorded at review

and the routing path would filter on keccak256(await getCode(address)) === codeHash.

Two details that matter in practice:

  • Fail closed. If the code hash cannot be read, the hook should be excluded rather than admitted — an RPC failure should not become an admission.
  • Rotation is a review event. A legitimate upgrade produces a mismatch, which is correct: it should re-enter review rather than pass silently. That is the property being bought.

A related, much cheaper filter, if it is not already applied: BEFORE_SWAP_RETURNS_DELTA_FLAG and AFTER_SWAP_RETURNS_DELTA_FLAG are encoded in the hook's own address, so delta-returning hooks can be identified by bitmask with no call at all. Whether they should be routed is a policy question rather than a safety one, but separating them at least makes the decision explicit.

Context

We hit this while integrating v4 into an aggregator and ended up implementing the on-chain version — code hash pinned at admission, re-checked at routing time, with a timelocked two-step path for delta hooks: hook-safety-gate (MIT, zero dependencies).

Not proposing you adopt that contract — an off-chain router does not need it, and the on-chain design carries an owner-controlled admission step that would make no sense here. The part that seemed worth raising is the property, not the implementation.

Happy to open a PR adding the code-hash field and the filter if that would be useful, or to leave it as a note if you have already considered and rejected it — in which case the reasoning would be genuinely interesting to us.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions