-
Notifications
You must be signed in to change notification settings - Fork 530
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: saucepoint <[email protected]>
- Loading branch information
1 parent
014b361
commit dd04160
Showing
1 changed file
with
225 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,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 | ||
``` | ||
|