Skip to content

Commit

Permalink
docs: v4 hooks guides optimizations (#844)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xdevant authored Jan 17, 2025
1 parent 70c7e8a commit 869a0a1
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 204 deletions.
90 changes: 73 additions & 17 deletions docs/contracts/v4/guides/hooks/01-your-first-hook.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ Let’s start by defining when users will be rewarded with these points:

1. When the user swaps `ETH` into `TOKEN` they will get awarded points equal to how much `ETH` they swapped the token with.
2. When the user adds liquidity, we award them with points equal to the amount of `ETH` they added.
3. [todo]

In order to keep track of these points, we’ll mint the `POINTS` token to the user, this has an added benefit for the user to be able to track it in their wallet.

Expand All @@ -26,17 +25,81 @@ Let’s figure out how our hook will work.

From the Uniswap v4 Documentation, there are several hooks available for developers to integrate with. In our use case, we specifically need the ability to read swaps and figure out what amounts they are swapping for and who they are.

[consider adding a callout for Universal Router here]

For our hook, we’ll be using `afterSwap` and `afterAddLiquidity` hooks. Why these instead of the `before...` hooks? We’ll dig deeper into this later in this guide.

_Note: To initiate the swap in the first place, this is where [`UniversalRouter`](../../../../contracts/universal-router/01-overview.md) comes into play where we will pass in the [`V4_SWAP`](https://github.com/Uniswap/universal-router/blob/main/contracts/libraries/Commands.sol#L35) command to `UniversalRouter.execute`._

# Let’s create our hook!

We’ll call this hook `PointsHook` and create it in such a way that any pool paired with `TOKEN` can use it.

## Setting things up

[todo: base this on the new template repo]
The Uniswap [v4-template repo](https://github.com/uniswapfoundation/v4-template) provides a basic foundry environment with required imports already pre-loaded. Click on [`Use this template`](https://github.com/new?template_name=v4-template&template_owner=uniswapfoundation) to create a new repository with it.

Or simply clone it and install the dependencies:

```bash
git clone https://github.com/uniswapfoundation/v4-template.git
cd v4-template
# requires foundry
forge install
forge test
```

After that let's create a new contract `PointsHook.sol` in `src` folder with the following codes:

```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 PointsHook is BaseHook {
constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}
function getHookPermissions()
public
pure
override
returns (Hooks.Permissions memory)
{
return
Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: false,
afterAddLiquidity: true,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: false,
afterSwap: true,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
}
```

The above code does the following:

- import the relevant dependencies
- initialize the constructor by passing in the instance of 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.

Awesome! Now it's all set to start building the hook!

## Basic Structure

Expand Down Expand Up @@ -94,13 +157,13 @@ contract PointsHook is BaseHook {
}
```

You’ll notice that both hooks return their own selector in the functions, this is pattern used by the protocol to signal “successful” invocation. We’ll talk about rest of the return parameters when we start adding the functionality.
You’ll notice that both hooks return their own selector in the functions, this is a pattern used by the protocol to signal “successful” invocation. We’ll talk about rest of the return parameters when we start adding the functionality.

Most of the code at this point should be self-explanatory. It’s not doing anything yet, but it’s a great place to start adding the functionality we need.

## Points Logic

Up until here, the hook isn’t actually doing anything, so let’s add some functionality! First, let’s setup the `POINTS` token that we’ll reward people with.
First, let’s setup the `POINTS` token that we’ll reward users with via creating another contract `PointsToken.sol` and import relevant dependencies like `ERC20` and `Owned`.

```solidity
contract PointsToken is ERC20, Owned {
Expand All @@ -121,7 +184,7 @@ contract PointsHook is BaseHook {
constructor(IPoolManager _poolManager) BaseHook(_poolManager) {
pointsToken = new PointsToken();
}
[...]
}
```
Expand Down Expand Up @@ -242,15 +305,9 @@ Similar to what we did for the `afterSwap` hook, now we need to award users for

We’re using Foundry for building our hook, and we’ll continue using it to write our tests. One of the great things about Foundry is that you can write tests in Solidity itself instead of context switching between another language.

### Hook Contract Address

The `PositionManager` for Uniswap v4 expects the hook address to indicate supported flags.

[todo: this section is completely wrong on UHI atm and needs to be rewritten, should consider moving it out of here and have a singular place explaining hook bits]

### Test Suite

The starter repo you cloned already has an existing base test file, let’s start by copying it into `PointsHook.t.sol`.
The v4-template repo you cloned already has an existing base test file, let’s start by copying it into `PointsHook.t.sol`.

```solidity
contract PointsHookTest is Test, Fixtures {
Expand Down Expand Up @@ -377,7 +434,6 @@ function test_PointsHook_Liquidity() public {
liqToAdd
);
// Let's swap some ETH for the token.
posm.mint(
key,
tickLower,
Expand All @@ -399,6 +455,6 @@ function test_PointsHook_Liquidity() public {

This test case looks very similar to the `afterSwap` one, except we’re testing based on the liquidity added. You’ll notice at the end we’re testing for approximate equality within 10 points. This is to account for minor differences in actual liquidity added due to ticks and pricing.

# What’s next?
# Next Steps

[todo: explain docs and introduce advance concepts]
Congratulations on building your very first hook! You could explore further by going to [Hook Deployment](./05-hook-deployment.mdx) to learn about how hook flags work and see how we will deploy a hook in action.
186 changes: 0 additions & 186 deletions docs/contracts/v4/guides/hooks/04-Volatility-fee-hook.mdx

This file was deleted.

2 changes: 1 addition & 1 deletion docs/contracts/v4/guides/hooks/05-hook-deployment.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ A full list of all flags can be found [here](https://github.com/Uniswap/v4-core/

## Hook Miner

Because of encoded addresses, hook developers must _mine_ an address to a their particular pattern
Because of encoded addresses, hook developers must _mine_ an address to a their particular pattern.

For local testing, `deployCodeTo` cheatcode in Foundry can be used to deploy hook contract to any address.

Expand Down

0 comments on commit 869a0a1

Please sign in to comment.