Skip to content

Commit

Permalink
Add NoOp hooks guide with mdx formatting (#756)
Browse files Browse the repository at this point in the history
  • Loading branch information
krisoshea-eth authored Sep 17, 2024
1 parent 754d3e9 commit c27116d
Showing 1 changed file with 131 additions and 0 deletions.
131 changes: 131 additions & 0 deletions docs/contracts/v4/guides/04-hooks/02-NoOp-hooks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
title: NoOp Hooks
---

One feature enabled by custom accounting is​​​​‌ NoOp swap. This feature allows hook developers to replace the v4 (v3-style) swap logic.

This means developers can replace Uniswap's internal core logic for how to handle swaps. Two emergent use-cases are possible with NoOp:

1. Asynchronous swaps and swap-ordering. Delay the v4 swap logic for fulfillment at a later time.
2. Custom Curves. Replace the v4 swap logic with different swap logic. The custom logic is flexible and developers can implement symmetric curves, asymmetric curves, or custom quoting.

> NoOp is typically described as taking the full input to replace the internal swap logic, partially taking the input is better described as *custom accounting*
Note: The flexibility of NoOp means hook developers can implement harmful behavior (such as taking all swap amounts for themselves, charging extra fees, etc.). Hooks with NoOp behavior should be examined very closely by both developers and users.

# Configure a NoOp Hook

To enable NoOp, developers will need the hook permission `BEFORE_SWAP_RETURNS_DELTA_FLAG`

```solidity
import {BaseHook} from "v4-periphery/BaseHook.sol";
// ...
contract NoOpHook is BaseHook {
// ...
function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: false,
afterAddLiquidity: false,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: false,
afterSwap: true,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: true,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
// ...
}
```

# beforeSwap

NoOp only works on exact-input swaps and the *beforeSwap* **must** take the input currency and return `BeforeSwapDelta`. The hook should `IPoolManager.mint` itself the corresponding tokens equal to the amount of the input (`amountSpecified`). It should then return a `BeforeSwapDelta` where `deltaSpecified = -amountSpecified` (the positive amount).

The funds' movements are as follows:

1. User initiates a swap, specifying -100 tokenA as input
2. The hook's beforeSwap takes 100 tokenA for itself, and returns a value of 100 to PoolManager.
3. The PoolManager accounts the 100 tokens against the swap input, leaving 0 tokens remaining
4. The PoolManager does not execute swap logic, as there are no tokens left to swap
5. The PoolManager transfers the delta from the hook to the swap router, in step 2 the hook created a debt (that must be paid)
6. The swap router pays off the debt using the user's tokens

```solidity
contract NoOpHook is BaseHook {
// ...
function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata)
external
override
returns (bytes4, BeforeSwapDelta, uint24)
{
// NoOp only works on exact-input swaps
if (params.amountSpecified < 0) {
// take the input token so that v3-swap is skipped...
Currency input = params.zeroForOne ? key.currency0 : key.currency1;
uint256 amountTaken = uint256(-params.amountSpecified);
poolManager.mint(address(this), input.toId(), amountTaken);
// to NoOp the exact input, we return the amount that's taken by the hook
return (BaseHook.beforeSwap.selector, toBeforeSwapDelta(amountTaken.toInt128(), 0), 0);
}
else {
return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO, 0);
}
}
}
```

# Testing

To verify the NoOp behaved properly, developers should test the swap and that token balances match expected behavior.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import {Deployers} from "v4-core/test/utils/Deployers.sol";
// ...
contract NoOpSwapTest is Test, Deployers {
// ...
function setUp() public {
// ...
}
function test_noOp() public {
assertEq(hook.beforeSwapCount(poolId), 0);
uint256 balance0Before = currency0.balanceOfSelf();
uint256 balance1Before = currency1.balanceOfSelf();
// Perform a test swap //
int256 amount = -1e18;
bool zeroForOne = true;
BalanceDelta swapDelta = swap(poolKey, zeroForOne, amount, ZERO_BYTES);
// ------------------- //
uint256 balance0After = currency0.balanceOfSelf();
uint256 balance1After = currency1.balanceOfSelf();
// user paid token0
assertEq(balance0Before - balance0After, 1e18);
// user did not recieve token1 (NoOp)
assertEq(balance1Before, balance1After);
}
}
```

0 comments on commit c27116d

Please sign in to comment.