From 1b18e0df84e20bd83d9875c61aa0244cfd0e2f7d Mon Sep 17 00:00:00 2001 From: ant <0xdevant@gmail.com> Date: Wed, 18 Sep 2024 15:25:06 +0800 Subject: [PATCH] v4 guides: hook swap (#766) --- docs/contracts/v4/guides/04-hooks/swap.mdx | 235 +++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 docs/contracts/v4/guides/04-hooks/swap.mdx diff --git a/docs/contracts/v4/guides/04-hooks/swap.mdx b/docs/contracts/v4/guides/04-hooks/swap.mdx new file mode 100644 index 000000000..fb2bfc41a --- /dev/null +++ b/docs/contracts/v4/guides/04-hooks/swap.mdx @@ -0,0 +1,235 @@ +--- +title: Swap Hooks +--- + +Swaps are the most common interaction with the Uniswap protocol. When it comes to swap there are two hook functions available to customize and extend its behavior: + +- `beforeSwap` +- `afterSwap` + +As the names suggest `beforeSwap`/`afterSwap` are functions called before or after a swap is executed on a pool. + +This guide will explain the mechanism of encoded flags for hooks, and go through the parameters and examples for `beforeSwap` and `afterSwap`. + +Note: The swap examples are not production ready code, and are implemented in a simplistic manner for the purpose of learning. + +## Hook Flags + +As mentioned in [Concept of Hooks](../../concepts/04-hooks.mdx), hook contracts indicate their implemented functions by __encoding flags in the address of the contract__. The `PoolManager` uses these permissions to determine which hook functions to call for a given pool. + +Each hook function e.g. `beforeSwap` - corresponds to a certain _flag_. For example, the `beforeSwap` function is correlated to the [`BEFORE_SWAP_FLAG`](https://github.com/Uniswap/v4-core/blob/main/src/libraries/Hooks.sol#L37) which has a value of `1 << 7`. + +These flags represent specific bits in the address of the hook smart contract - and the value of the bit (a one or a zero) represents whether that flag is true or false. An example: + +Addresses on Ethereum are 20 bytes long (160 bits). So for example the address: + +``` +0x00000000000000000000000000000000000000C0 +``` + +represented in binary is: + +```solidity +0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 +0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 +0000 0000 0000 0000 0000 0000 1100 0000 +``` + +In binary it goes from right-to-left - so the trailing 8 bits of this address are `1100 0000` where: + +1st Bit to 6th Bit = `0` + +7th Bit to 8th Bit = `1` + +The `AFTER_SWAP` flag is represented by the 7th bit - which is set to `1` for the example contract address. In the `PoolManager's` swap execution flow, it will observe the flag and make a call to the hook's `afterSwap` function. + +Similarly, the 8th bit which is also a `1`, actually corresponds to the `BEFORE_SWAP` i.e. the `beforeSwap` hook function - which will also be called by the `PoolManager` during a `swap` workflow. + +A full list of all flags can be found [here](https://github.com/Uniswap/v4-core/blob/main/src/libraries/Hooks.sol). + +## Set Up the Contract + +Declare the solidity version used to compile the contract, since transient storage is used the solidity version will be `>=0.8.24`. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; +``` + +Import the relevant dependencies from `v4-core` and `v4-periphery`: + +```solidity +import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol"; + +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol"; +import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol"; +``` + +Create a contract called SwapHook, use `PoolIdLibrary` to attach functions of computing ID of a pool to `PoolKey`. Declare two mappings to act as counters when calling `beforeSwap` and `afterSwap`. + +```solidity +contract SwapHook is BaseHook { + using PoolIdLibrary for PoolKey; + + // NOTE: --------------------------------------------------------- + // state variables should typically be unique to a pool + // a single hook contract should be able to service multiple pools + // --------------------------------------------------------------- + + mapping(PoolId => uint256 count) public beforeSwapCount; + mapping(PoolId => uint256 count) public afterSwapCount; + + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} +``` + +Override `getHookPermissions` from `BaseHook.sol` to return a struct of permissions to signal which hook functions are to be implemented. +It will also be used at deployment to validate the address correctly represents the expected permissions. + +```solidity +function getHookPermissions() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: true, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); +} +``` + +## beforeSwap + +Here the example shows that every time __before__ a swap is executed in a pool, `beforeSwapCount` for that pool will be incremented by one. + +```solidity +function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) + external + override + returns (bytes4, BeforeSwapDelta, uint24) +{ + beforeSwapCount[key.toId()]++; + return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); +} +``` + +### `beforeSwap` Parameters + +When triggering the `beforeSwap` hook function, there are some parameters we can make use of to customize or extend the behavior of `swap`. These parameters are described in `beforeSwap` from [`IHooks.sol`](https://github.com/Uniswap/v4-core/blob/main/src/interfaces/IHooks.sol#L111). + +A brief overview of the parameters: +- `sender` The initial `msg.sender` for the `PoolManager.swap` call. Typically a swap router +- `key` The key for the pool +- `params` The parameters for the swap i.e. `SwapParams` from [`IPoolManager.sol`](https://github.com/Uniswap/v4-core/blob/main/src/interfaces/IPoolManager.sol#L146C12-L146C22) +- `hookData` Arbitrary data handed into the `PoolManager` by the swapper to be be passed on to the hook + +## afterSwap + +Similiar as above, every time __after__ a swap is executed in a pool, `afterSwapCount` for that pool will be incremented by one. + +```solidity +function afterSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) + external + override + returns (bytes4, int128) +{ + afterSwapCount[key.toId()]++; + return (BaseHook.afterSwap.selector, 0); +} +``` + +### `afterSwap` Parameters + +When triggering the `afterSwap` hook function, there are some parameters we can make use of to customize or extend the behavior of `swap`. These parameters are described in `afterSwap` from [`IHooks.sol`](https://github.com/Uniswap/v4-core/blob/main/src/interfaces/IHooks.sol#L126). + +A brief overview of the parameters: +- `sender` The initial `msg.sender` for the `PoolManager.swap` call. Typically a swap router +- `key` The key for the pool +- `params` The parameters for the swap i.e. `SwapParams` from [`IPoolManager.sol`](https://github.com/Uniswap/v4-core/blob/main/src/interfaces/IPoolManager.sol#L146C12-L146C22) +- `delta` The amount owed to the caller (positive) or owed to the pool (negative) +- `hookData` Arbitrary data handed into the `PoolManager` by the swapper to be be passed on to the hook + + + +## A Complete Swap Hook Contract + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol"; + +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol"; +import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol"; + +contract SwapHook is BaseHook { + using PoolIdLibrary for PoolKey; + + // NOTE: --------------------------------------------------------- + // state variables should typically be unique to a pool + // a single hook contract should be able to service multiple pools + // --------------------------------------------------------------- + + mapping(PoolId => uint256 count) public beforeSwapCount; + mapping(PoolId => uint256 count) public afterSwapCount; + + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: true, + afterAddLiquidity: false, + beforeRemoveLiquidity: true, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: true, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + // ----------------------------------------------- + // NOTE: see IHooks.sol for function documentation + // ----------------------------------------------- + + function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) + external + override + returns (bytes4, BeforeSwapDelta, uint24) + { + beforeSwapCount[key.toId()]++; + return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function afterSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) + external + override + returns (bytes4, int128) + { + afterSwapCount[key.toId()]++; + return (BaseHook.afterSwap.selector, 0); + } +} +``` \ No newline at end of file