/uniswap-v4-hooks

A playground for Uniswap v4 hooks

Primary LanguageSolidityMIT LicenseMIT

A Playground for Uniswap v4 Hooks

Uniswap v4 Feature Summary

  • Lifecycle Hooks: initialize, position, swap and donate
  • Hook managed fees
    • swap and/or withdraw
    • static or dynamic
  • Swap and withdraw protocol fees
  • ERC-1155 accounting of multiple tokens
  • Native ETH pools like V1
  • Donate liquidity to pools

Contracts Diagrams

Contract dependencies

Uniswap v4 Contract dependencies

Install

This project uses Foundry to manage dependencies, compile contracts, test contracts and run a local node. See Foundry installation for instructions on how to install Foundry which includes forge and anvil.

git clone git@github.com:naddison36/uniswap-v4-hooks.git
cd uniswap-v4-hooks
forge install
forge update v4-periphery

Unit Testing

The following will run the unit tests in test/CounterScript

forge test -vvv

Counter Hook Example

  1. The CounterHook demonstrates the beforeModifyPosition, afterModifyPosition, beforeSwap and afterSwap hooks.
  2. The CounterScript deploys the v4 pool manager, test tokens, counter hook and test routers. It then sets up a pool, adds token liquidity and performs a swap.
  3. The CounterScript script deploys to a local Anvil node and does a swap.

CounterHook Contract

Local Testing

Because v4 exceeds the bytecode limit of Ethereum and it's business licensed, we can only deploy & test hooks on a local node like Anvil.

The following runs the script/CounterScript Forge script against a local Anvil node that:

  • Deploys the Uniswap v4 PoolManager
  • Uses the CounterFactory to deploy a CounterHook contract with the correct address prefix.
  • Creates a new pool with CounterHook as the hook.
  • Adds token liquidity to the pool
  • Performs a token swap
# start anvil with a larger code limit
anvil --code-size-limit 30000
# in a new terminal, run the Forge script
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
forge script script/CounterScript.sol \
    --rpc-url http://localhost:8545 \
    --code-size-limit 30000 \
    --broadcast

WARNING The above private key for account 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 is only used for Foundry testing. Do not use this known account on mainnet.

# Get the counter values from the CounterHook
export COUNTER=0x3CD91b522f2a6CB67F378d7fBee767602d5140bB
cast call $COUNTER "beforeSwapCounter()(uint256)" --rpc-url http://localhost:8545
cast call $COUNTER "afterSwapCounter()(uint256)" --rpc-url http://localhost:8545

Summary of the modify position calls

Counter Modify Summary

Summary of the swap calls

Counter Swap Summary

See here for more detailed transaction traces with call parameters and events.

My Hook

  1. The MyHook contract has empty beforeInitialize, afterInitialize, beforeModifyPosition, afterModifyPosition, beforeSwap, afterSwap, beforeDonate and afterDonate hooks that can be implemented.
  2. The MyHookScript script deploys to a local Anvil node and does a swap.
# start anvil with a larger code limit
anvil --code-size-limit 30000
# in a new terminal, run the Forge script
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
forge script script/MyHookScript.sol \
    --rpc-url http://localhost:8545 \
    --code-size-limit 30000 \
    --broadcast

Dynamic Fee Hook

  1. The DynamicFeeHook contract has an empty getFee function that is used to set the fee for dynamic fee pools. It also has empty beforeModifyPosition, afterModifyPosition, beforeSwap and afterSwap hooks but they are not required for a dynamic fee hook. They can be removed if not needed.
  2. The DynamicFeeScript script deploys to a local Anvil node and does a swap. Note the fee in the PoolKey is set with the DYNAMIC_FEE_FLAG.
// Derive the key for the new pool
poolKey = PoolKey(
    Currency.wrap(address(token0)), Currency.wrap(address(token1)), FeeLibrary.DYNAMIC_FEE_FLAG, 60, hook
);
// Create the pool in the Uniswap Pool Manager
poolManager.initialize(poolKey, SQRT_RATIO_1_1);

DynamicFeeHook Contract

Summary of the swap calls

Dynamic Fee Swap Summary

See here for more examples and details.

Local Testing

# start anvil with a larger code limit
anvil --code-size-limit 30000

The following runs the DynamicFeeScript against a local Anvil node

# in a new terminal, run the Forge script
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
forge script script/DynamicFeeScript.sol \
    --rpc-url http://localhost:8545 \
    --code-size-limit 30000 \
    --broadcast

Contribution

This repository was created from this GitHub project template https://github.com/saucepoint/v4-template. Thanks @saucepoint for an excellent starting point. This repo has significantly evolved from the starting template.

Additional resources: