Hooks Contract Address
The hooks contract address will define which hooks functions will be called when a swap is executed. The last 10 bits of this address are flags that indicate which hook callbacks are implemented.
There are 10 potential hook functions and each one corresponds to a specific bit position.
| Bit Position | Hook Function |
|---|---|
| 0 | beforeInitialize |
| 1 | afterInitialize |
| 2 | beforeSwap |
| 3 | afterSwap |
| 4 | beforeAddLiquidity |
| 5 | afterAddLiquidity |
| 6 | beforeRemoveLiquidity |
| 7 | afterRemoveLiquidity |
| 8 | beforeDonate |
| 9 | afterDonate |
v4-core repository includes a Hooks library that help us with low level operations related to this flags.
It includes all flags:
uint160 internal constant BEFORE_INITIALIZE_FLAG = 1 << 13;
uint160 internal constant AFTER_INITIALIZE_FLAG = 1 << 12;
uint160 internal constant BEFORE_ADD_LIQUIDITY_FLAG = 1 << 11;
uint160 internal constant AFTER_ADD_LIQUIDITY_FLAG = 1 << 10;
uint160 internal constant BEFORE_REMOVE_LIQUIDITY_FLAG = 1 << 9;
uint160 internal constant AFTER_REMOVE_LIQUIDITY_FLAG = 1 << 8;
uint160 internal constant BEFORE_SWAP_FLAG = 1 << 7;
uint160 internal constant AFTER_SWAP_FLAG = 1 << 6;
uint160 internal constant BEFORE_DONATE_FLAG = 1 << 5;
uint160 internal constant AFTER_DONATE_FLAG = 1 << 4;
uint160 internal constant BEFORE_SWAP_RETURNS_DELTA_FLAG = 1 << 3;
uint160 internal constant AFTER_SWAP_RETURNS_DELTA_FLAG = 1 << 2;
uint160 internal constant AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 1;
uint160 internal constant AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 0;There’s a hasPermission function that we can use to check if a specific hook is implemented in the hooks contract address.
function hasPermission(IHooks self, uint160 flag) internal pure returns (bool) {
return uint160(address(self)) & flag != 0;
}The Permissions struct that we’ll be use in our BaseHook implementation.
struct Permissions {
bool beforeInitialize;
bool afterInitialize;
bool beforeAddLiquidity;
bool afterAddLiquidity;
bool beforeRemoveLiquidity;
bool afterRemoveLiquidity;
bool beforeSwap;
bool afterSwap;
bool beforeDonate;
bool afterDonate;
bool beforeSwapReturnDelta;
bool afterSwapReturnDelta;
bool afterAddLiquidityReturnDelta;
bool afterRemoveLiquidityReturnDelta;
}When we define our BaseHook implementation, we’ll override the getHookPermissions function to specify which hooks we want to use.
For example:
function getHookPermissions()
public
pure
override
returns (Hooks.Permissions memory)
{
return
Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: true,
afterAddLiquidity: false,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: false,
afterSwap: false,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}Besides setting the permissions in the getHookPermissions function, it isn’t enough. We also need to ensure that:
- The desired hook functions are implemented in the hooks contract. If we don’t override the internal function inherited from
BaseHook, the hook call will revert the transaction. - The flags are set correctly in the hooks contract address.
For this last part, we can call the validateHookPermissions function from the Hooks library in the constructor of our hooks contract.
This function will check every bit position and revert the deployment transaction if the flags are not set correctly in the hooks contract address.
constructor() {
IHooks(this).validateHookPermissions(
Hooks.Permissions({
beforeInitialize: true,
afterInitialize: true,
beforeAddLiquidity: true,
afterAddLiquidity: true,
beforeRemoveLiquidity: true,
afterRemoveLiquidity: true,
beforeSwap: true,
afterSwap: true,
beforeDonate: true,
afterDonate: true,
beforeSwapReturnDelta: true,
afterSwapReturnDelta: true,
afterAddLiquidityReturnDelta: true,
afterRemoveLiquidityReturnDelta: true
})
);
}