Continuity track · Uniswap v4 · Arbitrum Stylus

Stylus Hook

Uniswap v4 hooks written entirely in Rust

No Solidity anywhere in the hook. The contract the PoolManager calls is the Stylus contract, and it lands on its own permission address.

stylus-hook1 / 16
Where this started

Scaling Ethereum 2024

The original adapted a Uniswap limit-order hook to Stylus and won 1st place in the Arbitrum Stylus track.

stylus-hook2 / 16
Why it stopped there

Both sides were pre-production

Uniswap v4 was still being built

Unreleased and moving. The version of the day needed transient storage Arbitrum did not have, so the repo had to vendor a no-cancun fork of the protocol to run at all.

Stylus was not on mainnet

Testnet only. And it could not deploy a contract to a mined CREATE2 address, which is the one thing a hook needs, so the hook had to be Solidity.

You cannot finish a bridge between two things that are both still moving. The honest call was to stop and wait.

stylus-hook3 / 16
What changed

Both shipped

Every reason to stop in 2024 had been removed. So we finished it.

stylus-hook4 / 16
The whole problem

v4 asks a hook for exactly two things

1. Answer the IHooks ABI

Free. A Stylus contract is ABI-equivalent to a Solidity one, so the ten callbacks just work.

2. Live at a flagged address

The low 14 bits encode the callbacks. The PoolManager reads them off the address and never calls one it does not see. This was the blocker.

The ABI was never the obstacle. The address was.

stylus-hook5 / 16
What we built

Three pieces, no Solidity

stylus/base-hook

BaseHook.sol's counterpart in Stylus: v4 types, permission flags, all ten callbacks, and the calls back into the singleton.

#[guarded_hooks]

Rust has no abstract types, so a proc macro inserts the caller and pool-key checks that Solidity gets from inheritance. Costs nothing.

stylus/hook-miner

Mines the CREATE2 salt whose StylusDeployer address carries exactly the declared flags.

A test asserts all ten callback selectors computed from the Rust ABI equal those in the compiled IHooks.sol. A Rust hook is only a hook if the calls land on the right methods.

stylus-hook6 / 16
Writing one

Five steps

// 1. storage and entry point — the hook owns its state
#[storage] #[entrypoint]
pub struct Counter { before_swap_count: StorageMap<FixedBytes<32>, StorageU256> }

// 2. declare the permissions the address will encode
fn permissions(&self) -> Permissions { Permissions::none().with_before_swap() }

// 3. constructor validates the mined address, or reverts
HookGuards::validate_hook_address(self)

// 4. implement only the declared callbacks
#[guarded_hooks] #[public]
impl IHooks for Counter { fn before_swap(..) { /* guards already inserted */ } }

// 5. mine the salt, then deploy to the address it prints
cargo run -p stylus-hook-miner -- --permissions before-swap
stylus-hook7 / 16
Proof

A real PoolManager, driving a Rust hook

./prove-native-hook.bash stands up an Arbitrum Nitro node, mines the address, deploys, and exercises every declared callback.

ok  landed on the mined address
ok  the code at that address is an activated Stylus program
ok  its constructor ran and validated the address
ok  beforeAddLiquidity reached the Rust hook
ok  beforeSwap reached the Rust hook
ok  afterSwap reached the Rust hook
ok  beforeRemoveLiquidity reached the Rust hook
ok  beforeDonate reverts with HookNotImplemented
stylus-hook8 / 16
So is it worth it?

Stylus is not faster. It is differently priced.

Entering · worse

About 7,900 gas per call that Solidity never pays, even with the program cached.

Storage · identical

Same slots, same trie, same EVM prices. Measured 1.06×. There is no such thing as Stylus storage.

Computing · far better

3× to 640× cheaper, depending on how badly the work fits a 256-bit word.

So a port pays off only above roughly 12,000 gas of Solidity arithmetic per call. That is the whole decision.

stylus-hook9 / 16
Where the lines cross

89 operations (mulDiv)

Rust on Stylus Solidity
0k1,000k2,000k3,000k they cross at 89 Solidity Rust 0502001,0005,000mulDiv operations per swap
stylus-hook10 / 16
When NOT to use it

Most hooks should stay in Solidity

stylus-hook11 / 16
When to use it

The gain tracks how badly the work fits a 256-bit word

xorshift64, 64-bit words41.6×integer sqrt5.6×plain a*b/c5.5×mulDiv, 512-bit3.4×rpow in Q963.0×one storage write1.06×

Storage barely moves. Everything that is real arithmetic does.

stylus-hook12 / 16
The case that matters

A TWAMM under real order flow

Against akshatmittal/v4-twamm-hook by Uniswap Labs and Zaha Studio, audited by ABDK and Certora, live on Base and Unichain.

Rust on Stylus Production Solidity
0k1,000k2,000k3,000k Solidity 2,849,561 Rust 1,891,213 saves 958,348 12481632concurrent order streams caught up in one swap
stylus-hook13 / 16
Why that shape is the point

The fee is paid once. The saving is paid per stream.

Streams at onceRustSoliditySaving
idle147,365132,321−15,044
1340,930351,474+10,544
8695,352880,744+185,392
321,891,2132,849,561+958,348

Each extra stream costs 52,163 gas in Rust against 83,256 in Solidity. On a busy protocol the quiet benchmark is the worst case, and it already wins.

stylus-hook14 / 16
What Solidity cannot do at all

Post-quantum signatures

SolidityRustRatio
One Keccak-f[1600] permutation101,662158643×
SHAKE256, 200 B in, 1088 B out1,250,2632,559489×
One ML-DSA verification11,353,43733,372340×

keccak256 has the 0x01 pad compiled in and SHAKE pads with 0x1f, so SHAKE cannot come from any built-in, in either language. A swap costs ~115,000 gas. Solidity would spend ninety-nine swaps to check one signature. Rust spends a third of one.

stylus-hook15 / 16
Take it

Write your v4 hook in Rust

  • stylus/base-hook — the ten callbacks, ready to build on
  • stylus/hook-miner — the address, solved
  • Seven benchmarks that tell you honestly whether to bother
  • FEEDBACK.md — what porting v4-core taught us

github.com/youtpout/stylus-hook

stylus-hook16 / 16
← docs  ·  → move · N notes · F fullscreen