-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d344f3f
commit 7a025d8
Showing
10 changed files
with
293 additions
and
5 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,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); | ||
} | ||
} |
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
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
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,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, ...); |
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,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">// <-- 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 |
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,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}}} | ||
``` |
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,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 |
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
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
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