Skip to content

Commit

Permalink
v4 guides: hook swap (#766)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xdevant committed Sep 18, 2024
1 parent 4522d97 commit 1b18e0d
Showing 1 changed file with 235 additions and 0 deletions.
235 changes: 235 additions & 0 deletions docs/contracts/v4/guides/04-hooks/swap.mdx
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);
}
}
```

0 comments on commit 1b18e0d

Please sign in to comment.