Skip to content

Commit

Permalink
swap fee information
Browse files Browse the repository at this point in the history
  • Loading branch information
saucepoint committed Feb 1, 2024
1 parent d344f3f commit 7a025d8
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 5 deletions.
117 changes: 117 additions & 0 deletions forge-test/SwapFee.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import {IHooks} from "v4-core/interfaces/IHooks.sol";
import {Hooks} from "v4-core/libraries/Hooks.sol";
import {TickMath} from "v4-core/libraries/TickMath.sol";
import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/types/PoolKey.sol";
import {BalanceDelta} from "v4-core/types/BalanceDelta.sol";
import {PoolId, PoolIdLibrary} from "v4-core/types/PoolId.sol";
import {Constants} from "v4-core/../test/utils/Constants.sol";
import {CurrencyLibrary, Currency} from "v4-core/types/Currency.sol";
import {HookTest} from "@v4-by-example/utils/HookTest.sol";
import {HookMiner} from "./utils/HookMiner.sol";
import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol";
import {PoolSwapTest} from "v4-core/test/PoolSwapTest.sol";

contract SwapFeeTest is HookTest, GasSnapshot {
using PoolIdLibrary for PoolKey;
using CurrencyLibrary for Currency;

PoolKey poolKey;
PoolId poolId;

function setUp() public {
// creates the pool manager, test tokens, and other utility routers
HookTest.initHookTestEnv();

// Create the pool
poolKey =
PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, 60, IHooks(address(0x0)));
poolId = poolKey.toId();
initializeRouter.initialize(poolKey, Constants.SQRT_RATIO_1_1, ZERO_BYTES);

// Provide liquidity to the pool
modifyPositionRouter.modifyLiquidity(poolKey, IPoolManager.ModifyLiquidityParams(-60, 60, 10 ether), ZERO_BYTES);
modifyPositionRouter.modifyLiquidity(
poolKey, IPoolManager.ModifyLiquidityParams(-120, 120, 10 ether), ZERO_BYTES
);
modifyPositionRouter.modifyLiquidity(
poolKey,
IPoolManager.ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10 ether),
ZERO_BYTES
);
}

function test_zeroForOne_exactInput() public {
uint256 balance0Before = token0.balanceOf(address(this));
uint256 balance1Before = token1.balanceOf(address(this));

// Perform a test swap //
int256 amount = 1e18;
bool zeroForOne = true;
BalanceDelta swapDelta = swap(poolKey, amount, zeroForOne, ZERO_BYTES);
// ------------------- //

uint256 balance0After = token0.balanceOf(address(this));
uint256 balance1After = token1.balanceOf(address(this));

assertEq(balance0Before - balance0After, 1e18);
assertApproxEqAbs(balance1After - balance1Before, 1e18, 0.1e18); // fee on output token
}

function test_zeroForOne_exactOutput() public {
uint256 balance0Before = token0.balanceOf(address(this));
uint256 balance1Before = token1.balanceOf(address(this));

// Perform a test swap //
int256 amount = -1e18;
bool zeroForOne = true;
BalanceDelta swapDelta = swap(poolKey, amount, zeroForOne, ZERO_BYTES);
// ------------------- //

uint256 balance0After = token0.balanceOf(address(this));
uint256 balance1After = token1.balanceOf(address(this));

assertApproxEqAbs(balance0Before - balance0After, 1e18, 0.1e18); // fee on input token
assertEq(balance1After - balance1Before, 1e18);
}

function test_oneForZero_exactInput() public {
uint256 balance0Before = token0.balanceOf(address(this));
uint256 balance1Before = token1.balanceOf(address(this));

// Perform a test swap //
int256 amount = 1e18;
bool zeroForOne = false;
BalanceDelta swapDelta = swap(poolKey, amount, zeroForOne, ZERO_BYTES);
// ------------------- //

uint256 balance0After = token0.balanceOf(address(this));
uint256 balance1After = token1.balanceOf(address(this));

// fee on output token 0
assertEq(balance1Before - balance1After, 1e18);
assertApproxEqAbs(balance0After - balance0Before, 1e18, 0.1e18);
}

function test_oneForZero_exactOutput() public {
uint256 balance0Before = token0.balanceOf(address(this));
uint256 balance1Before = token1.balanceOf(address(this));

// Perform a test swap //
int256 amount = -1e18;
bool zeroForOne = false;
BalanceDelta swapDelta = swap(poolKey, amount, zeroForOne, ZERO_BYTES);
// ------------------- //

uint256 balance0After = token0.balanceOf(address(this));
uint256 balance1After = token1.balanceOf(address(this));

// fee on input token
assertApproxEqAbs(balance1Before - balance1After, 1e18, 0.1e18);
assertEq(balance0After - balance0Before, 1e18);
}
}
7 changes: 7 additions & 0 deletions src/keywords.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@
"custom curve",
"custom accounting"
],
"/fees/swap-fee": [
"fees",
"swap",
"swap fee",
"lp fee",
"fee tier"
],
"/fees/fixed-hook-fee": [
"hook",
"hooks",
Expand Down
4 changes: 4 additions & 0 deletions src/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export const HOOK_ROUTES: Route[] = [
]

const FEE_ROUTES: Route[] = [
{
path: "swap-fee",
title: "Swap Fee"
},
{
path: "fixed-hook-fee",
title: "Static Hook Fee"
Expand Down
12 changes: 12 additions & 0 deletions src/pages/fees/swap-fee/SetSwapFee.solsnippet
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
uint24 swapFee = 500; // 0.05% swap fee, 3000 = 0.30%

// ----------------------------------------------- //

PoolKey memory pool = PoolKey({
currency0: Currency.wrap(token0),
currency1: Currency.wrap(token1),
fee: swapFee, // <-- Setting the Swap Fee -- //
tickSpacing: tickSpacing,
hooks: IHooks(address(0x0))
});
initializeRouter.initialize(pool, ...);
60 changes: 60 additions & 0 deletions src/pages/fees/swap-fee/index.html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// metadata
export const version = "0.8.20"
export const title = "Swap Fees"
export const description = "Swap Fees in v4"

export const keywords = [
"fees",
"swap",
"swap fee",
"lp fee",
"fee tier",
]

export const codes = [
{
fileName: "SetSwapFee.sol",
code: "dWludDI0IHN3YXBGZWUgPSA1MDA7IC8vIDAuMDUlIHN3YXAgZmVlLCAzMDAwID0gMC4zMCUKCi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIC8vCgpQb29sS2V5IG1lbW9yeSBwb29sID0gUG9vbEtleSh7CiAgICBjdXJyZW5jeTA6IEN1cnJlbmN5LndyYXAodG9rZW4wKSwKICAgIGN1cnJlbmN5MTogQ3VycmVuY3kud3JhcCh0b2tlbjEpLAogICAgZmVlOiBzd2FwRmVlLCAgICAgICAgICAgICAgICAgICAgIC8vIDwtLSBTZXR0aW5nIHRoZSBTd2FwIEZlZSAtLSAvLwogICAgdGlja1NwYWNpbmc6IHRpY2tTcGFjaW5nLAogICAgaG9va3M6IElIb29rcyhhZGRyZXNzKDB4MCkpCn0pOwppbml0aWFsaXplUm91dGVyLmluaXRpYWxpemUocG9vbCwgLi4uKTsK",
},
]

const html = `<p>Swap fees are accrued to liquidity providers and paid by swappers. The core logic and accrual design is exactly the same as v3.</p>
<p>In v3, there were four fixed fee-tiers <code>0.01%, 0.05%, 0.30%, and 1.0%</code>. In v4, fee-tiers are continuous</p>
<h3>Fee Charging</h3>
<p>Swap fees are charged on the "open" side of a trade</p>
<ul>
<li>exact-input: fees are taken from the output token</li>
<li>exact-output: fees are taken on the input token</li>
</ul>
<p>Example:</p>
<ul>
<li><strong>Exact Input</strong>: User is swapping exactly 100 USDC into ETH: fee is taken from the <strong>ETH output</strong></li>
<li><strong>Exact Output</strong>: User is willing to pay USDC for 0.01 ETH: fee is taken from the <strong>USDC input</strong></li>
</ul>
<h3>Note on Protocol Fee</h3>
<p>The protocol fee is <strong>not</strong> currently enabled. However, it is expressed as a percentage of the swap fee and <em>taken</em> from the swap fee</p>
<p>Example:</p>
<ul>
<li>swap fee 0.30%, protocol fee 0.10%</li>
<li>A swapper pays 1e18 in fees (0.30% of their swap size)</li>
<li>0.001e18 token (0.10% of 1e18) is taken for the protocol</li>
<li>0.999e18 token (99.9% of 1e18) is given the liquidity providers</li>
</ul>
<hr>
<h2>Example: Setting a Swap Fee</h2>
<p>The swap fee is set during pool creation, as defined in its <code>PoolKey</code></p>
<pre><code class="language-solidity"><span class="hljs-keyword">uint24</span> swapFee <span class="hljs-operator">=</span> <span class="hljs-number">500</span>; <span class="hljs-comment">// 0.05% swap fee, 3000 = 0.30%</span>
<span class="hljs-comment">// ----------------------------------------------- //</span>
PoolKey <span class="hljs-keyword">memory</span> pool <span class="hljs-operator">=</span> PoolKey({
currency0: Currency.<span class="hljs-built_in">wrap</span>(token0),
currency1: Currency.<span class="hljs-built_in">wrap</span>(token1),
fee: swapFee, <span class="hljs-comment">// &lt;-- Setting the Swap Fee -- //</span>
tickSpacing: tickSpacing,
hooks: IHooks(<span class="hljs-keyword">address</span>(<span class="hljs-number">0x0</span>))
});
initializeRouter.initialize(pool, ...);
</code></pre>`

export default html
43 changes: 43 additions & 0 deletions src/pages/fees/swap-fee/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: Swap Fees
version: 0.8.20
description: Swap Fees in v4
keywords: [fees, swap, swap fee, lp fee, fee tier]
---

Swap fees are accrued to liquidity providers and paid by swappers. The core logic and accrual design is exactly the same as v3.

In v3, there were four fixed fee-tiers `0.01%, 0.05%, 0.30%, and 1.0%`. In v4, fee-tiers are continuous

### Fee Charging

Swap fees are charged on the "open" side of a trade

- exact-input: fees are taken from the output token
- exact-output: fees are taken on the input token

Example:

- **Exact Input**: User is swapping exactly 100 USDC into ETH: fee is taken from the **ETH output**
- **Exact Output**: User is willing to pay USDC for 0.01 ETH: fee is taken from the **USDC input**

### Note on Protocol Fee

The protocol fee is **not** currently enabled. However, it is expressed as a percentage of the swap fee and _taken_ from the swap fee

Example:

- swap fee 0.30%, protocol fee 0.10%
- A swapper pays 1e18 in fees (0.30% of their swap size)
- 0.001e18 token (0.10% of 1e18) is taken for the protocol
- 0.999e18 token (99.9% of 1e18) is given the liquidity providers

---

## Example: Setting a Swap Fee

The swap fee is set during pool creation, as defined in its `PoolKey`

```solidity
{{{SetSwapFee}}}
```
29 changes: 29 additions & 0 deletions src/pages/fees/swap-fee/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react"
import Example from "../../../components/Example"
import html, { version, title, description, codes } from "./index.html"

interface Path {
path: string
title: string
}

interface Props {
prev: Path | null
next: Path | null
}

const ExamplePage: React.FC<Props> = ({ prev, next }) => {
return (
<Example
version={version}
title={title}
description={description}
html={html}
prev={prev}
next={next}
codes={codes}
/>
)
}

export default ExamplePage
2 changes: 1 addition & 1 deletion src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import styles from "./index.module.css"
import youTube from "../components/youtube.png"
import { ROUTES, ROUTES_BY_CATEGORY, TRANSLATIONS } from "../nav"

const UPDATES = ["2024/01/31 - msg.sender", "2024/01/23 - Dynamic Fees", "2024/01/08 - Quoter", "2023/12/15 - Update v4", "2023/12/11 - Custom Curves", "2023/12/03 - Static Hook Fee", "2023/11/28 - Updated pool initialization", "2023/11/28 - NoOp", "2023/11/13 - Make snippets concise", "2023/10/18 - Initial V4 Snippets"]
const UPDATES = ["2024/02/01 - Swap Fee", "2024/01/31 - msg.sender", "2024/01/23 - Dynamic Fees", "2024/01/08 - Quoter", "2023/12/15 - Update v4", "2023/12/11 - Custom Curves", "2023/12/03 - Static Hook Fee", "2023/11/28 - Updated pool initialization", "2023/11/28 - NoOp", "2023/11/13 - Make snippets concise", "2023/10/18 - Initial V4 Snippets"]

export default function HomePage() {
const [query, setQuery] = useState("")
Expand Down
5 changes: 5 additions & 0 deletions src/routes.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import component_create_liquidity from "./pages/create-liquidity"
import component_fees_dynamic_fee from "./pages/fees/dynamic-fee"
import component_fees_fixed_hook_fee from "./pages/fees/fixed-hook-fee"
import component_fees_swap_fee from "./pages/fees/swap-fee"
import component_hooks_custom_curve from "./pages/hooks/custom-curve"
import component_hooks_msg_sender from "./pages/hooks/msg-sender"
import component_hooks_no_op from "./pages/hooks/no-op"
Expand Down Expand Up @@ -38,6 +39,10 @@ const routes: Route[] = [
path: "/fees/fixed-hook-fee",
component: component_fees_fixed_hook_fee
},
{
path: "/fees/swap-fee",
component: component_fees_swap_fee
},
{
path: "/hooks/custom-curve",
component: component_hooks_custom_curve
Expand Down
19 changes: 15 additions & 4 deletions src/search.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"swap": [
"/swap",
"/hooks/no-op"
"/hooks/no-op",
"/fees/swap-fee"
],
"trade": [
"/swap"
Expand Down Expand Up @@ -89,6 +90,19 @@
"custom accounting": [
"/hooks/custom-curve"
],
"fees": [
"/fees/swap-fee",
"/fees/dynamic-fee"
],
"swap fee": [
"/fees/swap-fee"
],
"lp fee": [
"/fees/swap-fee"
],
"fee tier": [
"/fees/swap-fee"
],
"fee": [
"/fees/fixed-hook-fee",
"/fees/dynamic-fee"
Expand All @@ -99,9 +113,6 @@
"hook fee": [
"/fees/fixed-hook-fee"
],
"fees": [
"/fees/dynamic-fee"
],
"dynamic fee": [
"/fees/dynamic-fee"
],
Expand Down

0 comments on commit 7a025d8

Please sign in to comment.