Skip to content

Commit

Permalink
v4 guides: hook setup (#750)
Browse files Browse the repository at this point in the history
Co-authored-by: saucepoint <[email protected]>
  • Loading branch information
0xdevant and saucepoint authored Sep 17, 2024
1 parent 014b361 commit dd04160
Showing 1 changed file with 225 additions and 0 deletions.
225 changes: 225 additions & 0 deletions docs/contracts/v4/guides/04-hooks/setup.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
---
title: Set Up Local Environment
---

Before writing the hook let's first have a local environment properly configured e.g. deploying pool manager, utility routers and test tokens.

At the end of this guide a development environment will be set up to be used to build the rest of the examples in the Guides section of the docs.

To get started as quickly as possible for building Uniswap v4 hooks, there is a `Quick Start` section below to clone a boilerplate and get building. To start from scratch and learn the underlying concepts, jump to the `Start from Scratch` section.

# Quick Start

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
```

Then hop to the [Local Node via Anvil](#local-node-via-anvil) to complete the set up and start developing.

---

# Start from Scratch

In the following sections, let's walk through the steps to create the same environment set up as the boilerplate from scratch and learn the underlying concepts.

## Setting up Foundry

First thing is to set up a new Foundry project.

If there is no Foundry installed - follow the [Foundry Book](https://book.getfoundry.sh/getting-started/installation) for installation.

Once Foundry is setup, initialize a new project:

```bash
forge init counter-hook
cd counter-hook
```

Then install the Uniswap `v4-core` and `v4-periphery` contracts as dependencies:

```bash
forge install Uniswap/v4-core && forge install Uniswap/v4-periphery
```

Next, set up the remappings so that the shorthand syntax for importing contracts from the dependencies work nicely:

```bash
forge remappings > remappings.txt
```

> If there is something wrong with the inferred remappings, please replace with the following in `remappings.txt`:
```
@uniswap/v4-core/=lib/v4-core/
forge-gas-snapshot/=lib/v4-core/lib/forge-gas-snapshot/src/
forge-std/=lib/v4-core/lib/forge-std/src/
permit2/=lib/v4-periphery/lib/permit2/
solmate/=lib/v4-core/lib/solmate/
v4-core/=lib/v4-core/
v4-periphery/=lib/v4-periphery/
```

After that, remove the default Counter contract and its associated test and script file that Foundry initially set up. To do that, either manually delete those files, or just run the following:

```bash
rm ./**/Counter*.sol
```

Finally, since v4 uses transient storage which is only available after Ethereum's cancun hard fork and on Solidity versions >= 0.8.24 - some config must be set in `foundry.toml` config file.

To do that, add the following lines to `foundry.toml`:

```toml
# foundry.toml

solc_version = "0.8.26"
evm_version = "cancun"
ffi = true
```

Awesome! Now it's all set to start building the hook! Let’s run a quick test to confirm everything is set up properly.

## Compile a Basic Hook Contract

To confirm that the environment is configured correctly let's write a basic Counter Hook contract. Create a new file, `./src/CounterHook.sol` and paste the following code into it:

```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 CounterHook 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;
mapping(PoolId => uint256 count) public beforeAddLiquidityCount;
mapping(PoolId => uint256 count) public beforeRemoveLiquidityCount;
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);
}
function beforeAddLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) external override returns (bytes4) {
beforeAddLiquidityCount[key.toId()]++;
return BaseHook.beforeAddLiquidity.selector;
}
function beforeRemoveLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) external override returns (bytes4) {
beforeRemoveLiquidityCount[key.toId()]++;
return BaseHook.beforeRemoveLiquidity.selector;
}
}
```

To compile the Counter Hook contracts in the `./src` folder, use the foundry build command:

```bash
forge build
```

If the environment is compiled correctly it will display a message:

```
Compiler run successful!
```

## Local Node via Anvil

Other than writing unit tests, [Anvil](https://book.getfoundry.sh/anvil/) can be used to deploy and test hooks.

With the local node up and running, use the `--rpc-url 127.0.0.1:8545` flag in tests to point the Foundry testing suite to that local node:

```bash
# start anvil, a local EVM chain
anvil

# in a new terminal
# foundry script for deploying v4 & hooks to anvil
forge script script/Anvil.s.sol \
--rpc-url http://localhost:8545 \
--private-key <test_wallet_private_key> \
--broadcast

# test on the anvil local node
forge test --rpc-url 127.0.0.1:8545
```

# Next Steps
With the environment set up ready to be built on. Jump over to the guides section to learn about the Uniswap functions that can be integrated with Hook. Remember to add all contracts (.sol files) to the `./src` folder and their subsequent tests to the `./test` folder. Then test them against the local anvil node by running:

```bash
forge test --rpc-url 127.0.0.1:8545
```

0 comments on commit dd04160

Please sign in to comment.