-
Notifications
You must be signed in to change notification settings - Fork 546
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
235 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
``` |