├── .DS_Store ├── imgs ├── .DS_Store ├── hook_1_img1.png ├── uniswap-v4_1.png ├── uniswap-v3-delta-neutral_1.png ├── uniswap-v3-delta-neutral_2.png ├── uniswap-v3-delta-neutral_3.png ├── uniswap-v3-delta-neutral_4.png ├── uniswap-v3-delta-neutral_5.png └── uniswap-v3-delta-neutral_6.png ├── pricing ├── 5 │ ├── .DS_Store │ ├── image.png │ ├── image-1.png │ └── 5.md ├── 6 │ ├── .DS_Store │ ├── image.png │ ├── image-1.png │ ├── image-2.png │ ├── 6.md │ └── 6en.md ├── .DS_Store ├── Lambert的uniswap 隐含波动率的LVR证明.md ├── 4.md └── uniswap-v3-delta-neutral.md ├── gmx_v2 ├── 1_gmx_fund_struct.jpg └── 1_gmx介绍.md ├── uniswap-v4-hook ├── readme.md ├── 3_hook_address.md ├── 1_intro.md └── 2_first_hook.md ├── README.md ├── uniswap-v4-explore ├── 2_liquidity.md └── 1_pool.md ├── uniswap-v4_address_mining.md ├── uniswap-v2-pricing └── v2-pricing.markdown └── uniswap-v4-白皮书 └── readme.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/.DS_Store -------------------------------------------------------------------------------- /imgs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/imgs/.DS_Store -------------------------------------------------------------------------------- /pricing/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/pricing/.DS_Store -------------------------------------------------------------------------------- /pricing/5/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/pricing/5/.DS_Store -------------------------------------------------------------------------------- /pricing/5/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/pricing/5/image.png -------------------------------------------------------------------------------- /pricing/6/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/pricing/6/.DS_Store -------------------------------------------------------------------------------- /pricing/6/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/pricing/6/image.png -------------------------------------------------------------------------------- /imgs/hook_1_img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/imgs/hook_1_img1.png -------------------------------------------------------------------------------- /imgs/uniswap-v4_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/imgs/uniswap-v4_1.png -------------------------------------------------------------------------------- /pricing/5/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/pricing/5/image-1.png -------------------------------------------------------------------------------- /pricing/6/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/pricing/6/image-1.png -------------------------------------------------------------------------------- /pricing/6/image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/pricing/6/image-2.png -------------------------------------------------------------------------------- /gmx_v2/1_gmx_fund_struct.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/gmx_v2/1_gmx_fund_struct.jpg -------------------------------------------------------------------------------- /imgs/uniswap-v3-delta-neutral_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/imgs/uniswap-v3-delta-neutral_1.png -------------------------------------------------------------------------------- /imgs/uniswap-v3-delta-neutral_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/imgs/uniswap-v3-delta-neutral_2.png -------------------------------------------------------------------------------- /imgs/uniswap-v3-delta-neutral_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/imgs/uniswap-v3-delta-neutral_3.png -------------------------------------------------------------------------------- /imgs/uniswap-v3-delta-neutral_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/imgs/uniswap-v3-delta-neutral_4.png -------------------------------------------------------------------------------- /imgs/uniswap-v3-delta-neutral_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/imgs/uniswap-v3-delta-neutral_5.png -------------------------------------------------------------------------------- /imgs/uniswap-v3-delta-neutral_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/Zelos/main/imgs/uniswap-v3-delta-neutral_6.png -------------------------------------------------------------------------------- /uniswap-v4-hook/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # 目录 3 | 1. [Uniswap V4 hook intro](1_intro.md) 4 | 2. [First hook](2_first_hook.md) 5 | 3. [Hook address](3_hook_address.md) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 为什么我们关心 uniswap 2 | 3 | 我们可以举出很多原因 4 | 1. 价值密度最高的代码 5 | 2. DEX 对CEX 的优势 6 | 3. 即将发布uniswap v4 有潜在的应用场景 7 | 8 | 但更为本质的原因是相信去中心化这一技术是属于未来的。帮助陌生人达成交易的巨大正外部性是不可忽视的。 9 | 现代期权是在1792年纽约证券交易所成立后发展起来的,而1973年BSM定价模型才刚刚被提出。 10 | 下一个时代的交易平台的地基初步搭建完成,但是我们还有很多工作需要做,我们目前在下一个时代的1792,但是我们可以努力让1973来的更快。 11 | 12 | 13 | 14 | ## 在这里我们打算做些什么 15 | 16 | 我们会从三个方面做起,数学(模型),代码(构造),统计(现实),这三者将支撑我们对去中心化金融有更深刻的理解。 17 | 18 | 一些眼下的问题其实也远远还谈不上解决: 19 | 在代码上,我们要先了解运作机制 20 | 在数学上,我们可以回答流动性提供者面对的风险是什么,有没有定价公式? 21 | 在统计上,如果定价模型是合理的,目前的流动性提供者的收入是否匹配风险?流动性提供者对波动率的观点是什么? 22 | 23 | 我们在这个仓库会持续同步我们对这三个方向的理解。也会精选一些相关内容进行翻译。 24 | 25 | ## 参与贡献 26 | 27 | 如果你也想要加入 Zelos dex 小组,一起参与讨论/贡献,添加微信 coset2025 备注zelos/dex,快来一起build。 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /pricing/6/6.md: -------------------------------------------------------------------------------- 1 | # 6 隐含波动率 2 | 在之前的https://medium.com/zelos-research/an-lvr-approach-proof-of-guillaume-lambert-s-uniswap-v3-implied-volatility-6671883e46e2 中我们讨论了LVR 和 Lambert 能推导出相同的iv 公式。 3 | 4 | 在本篇中,我们会给出我们的隐含波动率计算方法,和一些有趣的结论,详细代码参考在https://github.com/zelos-alpha/uniswap-iv 5 | 6 | ## 隐含波动率的计算 7 | 本质上来说,当我们得到期权定价公式时这个问题已经解决了。 8 | $pv = f(\sigma,r,C,L,H)$ 9 | 10 | 其中$L,H$ 为做市范围,r 无风险利率和C 单位流动性手续费收益,通过简单的二分查找,找到对应的sigma即可求解。 11 | ```python 12 | def get_one_position_iv(r,mu,C,H,L,max_iterations=100,tolerance=1e-5): 13 | #use bisection method to find the iv,make the pv is 1 14 | lower,upper = 0.01,10 15 | for i in range(max_iterations): 16 | iv = (lower+upper)/2 17 | pv = uni_v3_pricing_euroexcu_gbm_version_analytic_general_solution(1,H,L,r,mu,C,iv) 18 | if pv<1: 19 | upper = iv 20 | else: 21 | lower = iv 22 | if abs(pv-1) DAI `添加流动性,但手头没有`DAI`。用户可以先将一部分`ETH `兑换为`DAI`,以便使用两种代币添加流动性。此外,用户还可以通过多跳交换(multi-hop swap)实现,例如从 `ETH` 兑换为`USDC` 再兑换为`DAI`。如果集成得当,用户只需要一次性转移`ETH` 即可完成整个操作。 22 | 23 | 在添加了如此多的改进之后,流动性管理的接口也发生了变化。在V3中,流动性管理的接口非常清晰,每一个操作都有对应的操作。比如在`NonfungiblePositionManager`合约中,流动性管理的函数名是: 24 | 25 | * mint 26 | * increaseLiquidity 27 | * decreaseLiquidity 28 | * collect 29 | * burn 30 | 31 | 而在`Pool`合约中,管理流动性的函数名是: 32 | 33 | * mint 34 | * collect 35 | * burn 36 | 37 | 这些函数名看起来清晰明了。但在V4中,为了让流动性操作有足够的灵活性,流动性管理的接口被重新设计。在`PositionManager`中,所有的操作被集成到`modifyLiquidities`函数中。在`PoolManager`中,操作也被集中到了`modifyLiquidity`函数。 38 | 39 | > 注意:`PoolManager`也有`mint`和`burn`函数,但是他们是用来管理ERC6909余额的,和V3中`mint`与`burn`的作用不同 40 | 41 | 以`PositionManager`为例,从`modifyLiquidities`这个名字可以看出,它是支持一次性多个操作的。事实也正是如此。它的设计思路有点像线性脚本,你可以设置多个动作,并设置参数,然后送到这个函数里一股脑执行。这里以创建流动性仓位举例, 42 | 43 | 1 首先创建一个`PositionManager`实例。 44 | 45 | ```solidity 46 | import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol"; 47 | 48 | IPositionManager posm = IPositionManager(
); 49 | ``` 50 | 51 | 2 设置动作 52 | 53 | 要创建一个新的流动性仓位,需要设置两个动作: 54 | 55 | 1. mint: 用来创建流动性仓位 56 | 2. 交易对: 设置添加哪两个token 57 | 58 | ```solidity 59 | import {Actions} from "v4-periphery/src/libraries/Actions.sol"; 60 | 61 | bytes memory actions = abi.encodePacked(Actions.MINT_POSITION, Actions.SETTLE_PAIR); 62 | ``` 63 | 64 | 3 设置参数,这两个参数对应上面的两个动作 65 | 66 | ```solidity 67 | bytes[] memory params = new bytes[](2); 68 | ``` 69 | 70 | 其中`MINT_POSITION`需要如下参数 71 | 72 | | 参数 | 类型 | 描述 | 73 | | ------------ | --------- | ------------------------------------------------------------ | 74 | | `poolKey` | *PoolKey* | 池的ID | 75 | | `tickLower` | *int24* | Position的lower tick | 76 | | `tickUpper` | *int24* | Position的upper tick | 77 | | `liquidity` | *uint256* | 流动性的数量 | 78 | | `amount0Max` | *uint128* | msg.sender可支付token0的最大数量 | 79 | | `amount1Max` | *uint128* | msg.sender可支付token1的最大数量 | 80 | | `recipient` | *address* | 接收流动性NFT(ERC-721)的地址 | 81 | | `hookData` | *bytes* | hook所需参数,这些参数将被直接转发给Hook | 82 | 83 | ```solidity 84 | Currency currency0 = Currency.wrap(); // 填写0地址表示使用ETH 85 | Currency currency1 = Currency.wrap(); 86 | PoolKey poolKey = PoolKey(currency0, currency1, 3000, 60, IHooks(hook)); 87 | 88 | params[0] = abi.encode(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData); 89 | ``` 90 | 91 | 而`SETTLE_PAIR` 动作需要两个参数: 92 | 93 | - `currency0` - msg.sender将支付的token 0的地址 94 | - `currency1` - msg.sender将支付的token 1的地址 95 | 96 | ```solidity 97 | params[1] = abi.encode(currency0, currency1); 98 | ``` 99 | 100 | 4 提交, 这里还可以设置一个过期时间。 101 | 102 | ```solidity 103 | uint256 deadline = block.timestamp + 60; 104 | 105 | uint256 valueToPass = currency0.isAddressZero() ? amount0Max : 0; 106 | 107 | posm.modifyLiquidities{value: valueToPass}( 108 | abi.encode(actions, params), 109 | deadline 110 | ); 111 | ``` 112 | 113 | 其它的操作(增加、移除流动性,收集手续费等)也与之类似。 114 | 115 | 在流动性的操作上,V4的改进并不大。这些改进更像是为了适应其它新功能而进行的升级。但即便如此,我们可以发现在V4的流动性管理上,功能更强,操作更方便。这些细致入微的改进,为流动性管理带来了更强的可操作性,同时为未来更复杂的 DeFi 场景奠定了基础。 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /pricing/4.md: -------------------------------------------------------------------------------- 1 | # part 4: two boundary stopping time 2 | 3 | 距离我们上一篇已经过去很久了,原因是我们为此写了一篇论文: https://arxiv.org/pdf/2411.12375 当然论文还需要补充一些内容和实验,但是其终点和本系列是一致的。后续我们会补充更多证明细节和金融直觉来帮助大家理解。 4 | 回到本篇。我们要证明下面的结论,这也是本系列中数学最多的一部分,需要用到最优停时定理和拉普拉斯变换。 5 | $$ 6 | \mathbb E(e^{-r\tau}|X_\tau=a)=\exp[{\mu(a-x)}]\frac{\sinh[(b-x)\sqrt{\mu^2+2r}]}{\sinh[(b-a)\sqrt{\mu^2+2r}]} 7 | $$ 8 | and 9 | $$ 10 | \mathbb E(e^{-r\tau}|X_\tau=b)=\exp[{\mu(b-x)}]\frac{\sinh[(x-a)\sqrt{\mu^2+2r}]}{\sinh[(b-a)\sqrt{\mu^2+2r}]} 11 | $$ 12 | 13 | Where $X_t = \mu t +W_t$ 14 | 15 | ## One boundary stopping time 16 | 17 | 我们首先考虑一个停时 18 | 19 | $$\tau_c = \min\{t\ge 0;X_t = c\},X_t = \mu t +W_t$$ 20 | 21 | 22 | 23 | 对于这个停时,我们要考虑下面的指数鞅 24 | $$ 25 | M_t:=\exp \left(\alpha X_t-\alpha \mu t-\frac{1}{2} \alpha^2 t\right) 26 | $$ 27 | 根据鞅停时定理 $E(M_{\tau}) = M(0)=1$ 28 | 由$X_\tau = c$ 可得: 29 | $$ 30 | \mathbb{E}(e^{\alpha c} \exp \left(-\left[\mu \alpha+\frac{1}{2} \alpha^2\right] \tau\right))=1 31 | $$ 32 | $$ 33 | \mathbb{E} [\exp \left(-\left[\mu \alpha+\frac{1}{2} \alpha^2\right] \tau\right)]=e^{-\alpha c} 34 | $$ 35 | 令$\lambda:=\mu \alpha+\frac{1}{2} \alpha^2$, 即 36 | 37 | $$\alpha = -\mu \pm \sqrt{\mu^2 + 2\lambda}$$ 38 | 39 | $$ 40 | \mathbb E(e^{-\lambda \tau})=\exp[(\mu \mp \sqrt{\mu^2+2\lambda})c] 41 | $$ 42 | 43 | 44 | 我们来考察双边停时问题。我们定义双边的停时,即 45 | 46 | $$ 47 | \tau_{ab}(x) = \sup\{t|ac>x$: 90 | $$ 91 | p(x \mid y, t)=\int_0^t f_c(x \mid \tau_c) p(c \mid y, t-\tau_c) d \tau 92 | $$ 93 | 94 | 我们实际上利用停时对区间$(0,t)$做了划分。恰好这也是卷积形式。我们后续不会用到这个公式,只是作为一个熟悉符号的练习。接下来我们对双边停时问题也做类似的事情,我们发现了如下的等式: 95 | $$ 96 | \begin{aligned} 97 | & f_b(x \mid t)=f_{a b}^{+}(x \mid t)+\int_0^t f_{a b}^{-}(x \mid \tau) f_b(a \mid t-\tau) d \tau \\ 98 | & f_a(x \mid t)=f_{a b}^{-}(x \mid t)+\int_0^t f_{a b}^{+}(x \mid \tau) f_a(b\mid t-\tau) d \tau 99 | \end{aligned} 100 | $$ 101 | 102 | 第一个等式实际上在描述:随机过程到 $b$的事件, 可以分解为两种情况: 103 | 1. 没有到a 之前先到b 104 | 2. 和先到a,再到b 105 | 106 | 积分我们不好处理,但是对于laplace 变换则非常熟悉,我们获得了新一组等式 107 | 108 | $$ 109 | \begin{aligned} 110 | & \hat{f}_b(x \mid \lambda)=\hat{f}_{a b}^{+}(x \mid \lambda)+\hat{f}_{a b}^{-}(x \mid \lambda) \hat{f}_b(a \mid \lambda) \\ 111 | & \hat{f}_a(x \mid \lambda)=\hat{f}_{a b}^{-}(x \mid \lambda)+\hat{f}_{a b}^{+}(x \mid \lambda) \hat{f}_a(b \mid \lambda) 112 | \end{aligned} 113 | $$ 114 | 115 | 这个方程组里有哪些是我么已知的内容呢. 116 | 117 | $\hat{f}_b(x \mid \lambda)$ 指的是从x出发,到b 的单边停时的laplace 变换。我们只需要把 $c>0$ 情况带入即可。$\exp[(\mu -\sqrt{\mu^2+2\lambda})(c-x)$。 118 | 那么$\hat{f}_b(a \mid \lambda)$,指的是从a出发,到达b 的停时,简单的平移一下,等价于从0 出发到(b-a)的停时的lpalace 变换 $\exp[(\mu -\sqrt{\mu^2+2\lambda})(b-a)$。 119 | 120 | 第二的等式也就不难理解,无非是要带入 $c<0$的情况。这两个等式里只有两个未知数了。求解方程组即可。 121 | 这里我们略过繁琐的计算代换,最终得到了如下的结果。 122 | 123 | $$ 124 | \begin{aligned} 125 | \hat{f}_{a b}^{-}(x \mid \lambda)&=\frac{\exp\{-[\mu(b+x)\}}{\exp\{-[\mu(b+a)\}} \frac{\sinh[(b-x)\sqrt{\mu^2+2\lambda}]}{\sinh[(b-a)\sqrt{\mu^2+2\lambda}]}\\ 126 | &= \exp[{\mu(a-x)}]\frac{\sinh[(b-x)\sqrt{\mu^2+2\lambda}]}{\sinh[(b-a)\sqrt{\mu^2+2\lambda}]} 127 | \end{aligned} 128 | $$ 129 | 130 | $$ 131 | \begin{aligned} 132 | \hat{f}_{a b}^{+}(x \mid \lambda)&=\frac{\exp\{-[\mu(a+x)\}}{\exp\{-[\mu(b+a)\}} \frac{\sinh[(x-a)\sqrt{\mu^2+2\lambda}]}{\sinh[(b-a)\sqrt{\mu^2+2\lambda}]}\\ 133 | &= \exp[{\mu(b-x)}]\frac{\sinh[(x-a)\sqrt{\mu^2+2\lambda}]}{\sinh[(b-a)\sqrt{\mu^2+2\lambda}]} 134 | \end{aligned} 135 | $$ 136 | 137 | 我们距离结果只剩一个问题了,我们是要求什么来着? 138 | 139 | $$ 140 | \mathbb E(e^{-r\tau}|X_\tau=a) 141 | $$ 142 | 143 | 这就是$\hat{f}_{a b}^{-}(x \mid \lambda)$,我们只需要将 无风险利率 $r$ 替换成 $\lambda$即可。 144 | $$ 145 | \mathbb E(e^{-r\tau}|X_\tau=a)=\hat{f}_{a b}^{-}(x \mid r)=\exp[{\mu(a-x)}]\frac{\sinh[(b-x)\sqrt{\mu^2+2r}]}{\sinh[(b-a)\sqrt{\mu^2+2r}]} 146 | $$ 147 | 同样的 148 | $$ 149 | \mathbb E(e^{-r\tau}|X_\tau=b)=\exp[{\mu(b-x)}]\frac{\sinh[(x-a)\sqrt{\mu^2+2r}]}{\sinh[(b-a)\sqrt{\mu^2+2r}]} 150 | $$ 151 | 152 | ## 下回预告 153 | 我们凑齐了数学工具了。开始定价吧。 -------------------------------------------------------------------------------- /uniswap-v4-hook/1_intro.md: -------------------------------------------------------------------------------- 1 | # Uniswap V4 Hook介绍 2 | 3 | 时隔三年, Uniswap终于发展到v4版本. Uniswap是一个非常有上进心的项目, 每次升级都会带来很大的变化, 比如从V2升级到V3实现了集中流动性, 从而提高了资金的利用率, 这使得Uniswap在AMM赛道中再次领先. 4 | 5 | 在V4中, 这种野心也表现的淋漓尽致. V4提供了很多新的功能, 如Hook机制, 单例合约, 闪电记账等. 其中最重要的是Hook机制. 它允许项目方在创建交易池时, 声明一个Hook合约, 用户在这个池进行流动性操作和swap操作时, 会触发对应的逻辑. 这个改变并不大, 从程序开发的角度来说, 不过是回调的应用罢了. 但从业务的角度来说, 带来的变化是翻天覆地的. 我们可以用BTC和ETH的关系作为类比, Bitcoin实现了代币的持有和交换, 而Ethereum在这个基础上附加了智能合约, 最终衍生出DeFi这个庞大市场. 在Ethereum之前, 很难想象这些语法简单的solidity代码能够衍生出近千亿美元的产业. 而Uniswap V4也是类似, 在V3完善的交易机制上, 允许项目方实现自己的逻辑, 从而衍生出各局想象力的项目. 6 | 7 | 要认识Hook, 首先要看看V4中的流动性池是什么样子. Uniswap V4中, 每个流动性池不再对应一个合约, 而是所有池统一在一个合约下.因此创建池的过程, 也从部署合约, 变成调用合约. 这是官方创建池的例子. 8 | 9 | ```solidity 10 | // SPDX-License-Identifier: MIT 11 | pragma solidity ^0.8.20; 12 | 13 | import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; 14 | import {IHooks} from "v4-core/src/interfaces/IHooks.sol"; 15 | import {PoolKey} from "v4-core/src/types/PoolKey.sol"; 16 | import {CurrencyLibrary, Currency} from "v4-core/src/types/Currency.sol"; 17 | 18 | contract PoolInitialize { 19 | using CurrencyLibrary for Currency; 20 | 21 | // set the initialize router 22 | IPoolManager manager = IPoolManager(address(0x01)); 23 | 24 | function init( 25 | address token0, 26 | address token1, 27 | uint24 swapFee, 28 | int24 tickSpacing, 29 | address hook, 30 | uint160 sqrtPriceX96, 31 | bytes calldata hookData 32 | ) external { 33 | // sort your tokens! v4 requires token0 < token1 34 | if (token0 > token1) { 35 | (token0, token1) = (token1, token0); 36 | } 37 | 38 | PoolKey memory pool = PoolKey({ 39 | currency0: Currency.wrap(token0), 40 | currency1: Currency.wrap(token1), 41 | fee: swapFee, 42 | tickSpacing: tickSpacing, 43 | hooks: IHooks(hook) 44 | }); 45 | manager.initialize(pool, sqrtPriceX96, hookData); 46 | } 47 | } 48 | ``` 49 | 50 | 在创建流动性池的过程中, 先声明了一个PoolKey, 在V4中, PoolKey是Pool的唯一ID, 也就是流动性池的身份证, 在这个ID中, 除了传入Token和手续费外, 还传入了Hook合约地址. 可见, 对于同样的币对和手续费, 如果Hook不同, 会被看作不同的流动性池. 这是和V3一个很大的区别. 51 | 52 | 而在真正创建池的时候(```manager.initialize(pool, sqrtPriceX96, hookData);```), 又传入了初始价格, 以及hookData. hookData可以将一些初始化的参数传递给Hook. 53 | 54 | 一个简单的Hook合约的例子如下: 55 | 56 | ```solidity 57 | // SPDX-License-Identifier: MIT 58 | pragma solidity ^0.8.20; 59 | 60 | import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol"; 61 | import {PoolKey} from "v4-core/types/PoolKey.sol"; 62 | import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol"; 63 | import {Hooks} from "v4-core/libraries/Hooks.sol"; 64 | 65 | contract SampleHook is BaseHook { 66 | constructor(IPoolManager _manager, string memory _name, string memory _symbol) BaseHook(_manager){ 67 | // do something 68 | } 69 | 70 | function getHookPermissions() public pure override returns (Hooks.Permissions memory) { 71 | return Hooks.Permissions({ 72 | beforeInitialize: false, 73 | afterInitialize: false, 74 | beforeAddLiquidity: true, 75 | beforeRemoveLiquidity: false, 76 | afterAddLiquidity: true, 77 | afterRemoveLiquidity: false, 78 | beforeSwap: false, 79 | afterSwap: false, 80 | beforeDonate: false, 81 | afterDonate: false, 82 | beforeSwapReturnDelta: false, 83 | afterSwapReturnDelta: false, 84 | afterAddLiquidityReturnDelta: false, 85 | afterRemoveLiquidityReturnDelta: false 86 | }); 87 | } 88 | function beforeAddLiquidity( 89 | address sender, 90 | PoolKey calldata key, 91 | IPoolManager.ModifyLiquidityParams calldata params, 92 | bytes override calldata hookData 93 | ) external returns (bytes4){ 94 | // do something 95 | } 96 | function afterAddLiquidity( 97 | address sender, 98 | PoolKey calldata key, 99 | IPoolManager.ModifyLiquidityParams calldata params, 100 | BalanceDelta delta, 101 | bytes calldata hookData 102 | ) external override returns (bytes4, BalanceDelta); { 103 | // do something 104 | } 105 | } 106 | ``` 107 | 108 | 从例子中我们可以看到,Hook是一个合约,而且继承于BaseHook。其中,BaseHook的getHookPermissions函数是必须重载的。通过这个函数,可以设置在哪些流程中添加逻辑: 109 | 110 | * 初始化前后 111 | * 添加流动性前后 112 | * 移除流动性前后 113 | * Swap前后 114 | * 捐赠(一个V4的新概念)前后 115 | * 在Swap返回金额变动之前 116 | * 在Swap, 添加流动性, 移除流动性的金额改变之后 117 | 118 | 比如我们想添加流动性之前和之后进行一些操作, 因此将beforeAddLiquidity, afterAddLiquidity设置为True. 119 | 120 | 最后, 重载beforeAddLiquidity和afterAddLiquidity函数, 每次这个池发生添加流动性交易之后, 就会执行这个函数中的逻辑. 121 | 122 | 整体流程如下图, 其中橙色代表PoolManager合约, 绿色代表Hook合约 123 | 124 | ```mermaid 125 | flowchart LR 126 | 127 | modifyLiquidity(["modifyLiquidity"]):::pool --> beforeAddLiquidity(["beforeAddLiquidity"]):::hook 128 | beforeAddLiquidity --> AddLiq(["Add Liquidity"]):::pool 129 | AddLiq --> afterAddLiquidity(["afterAddLiquidity"]):::hook 130 | classDef pool fill:#f96 131 | classDef hook fill:#9f6 132 | 133 | 134 | ``` 135 | 作为对比, V3中, 添加流动性的过程是这样的, 其中蓝色代表proxy合约, 橙色代表pool合约. 136 | 137 | ```mermaid 138 | flowchart LR 139 | 140 | mint(["Mint"]):::proxy --> AddLiquidity(["Add Liquidity"]):::pool 141 | classDef pool fill:#f96 142 | classDef proxy fill:#88f 143 | ``` 144 | Hook极大的扩展了流动性池的业务范围, 可以实现很多具备想象力的业务. 比如 145 | 146 | * 基于波动率或者时间的动态手续费 147 | 148 | * 将借贷平台整合进来, 把限制的流动性放入借贷平台做抵押, 防止资金空置. 149 | 150 | * 添加流动性的手续费享受复利. 151 | 152 | * 收取一部分手续费用于社区治理 153 | 154 | * Swap交易使用限价单机制. 155 | 156 | * 多签 157 | 158 | * KYC 159 | 160 | 161 | 162 | 因此, 只要想象力足够, 完全可以依托V4平台, 打造出自己的交易所, 甚至发明新的衍生品类型. 而V4可以提供流动性管理和交易的支持, 减少构建新项目的成本. 现在已经有很多团队开发了自己的Hook. 163 | 164 | ![uniswaphooks.com](../imgs/hook_1_img1.png) 165 | 166 | 但由于V4还没有发布, 线上的资源还很少, 连API文档都没发布. 如果想要进一步了解, 可以关注一下[官方的介绍](https://docs.uniswap.org/contracts/v4/overview), 以及[官方的例子](https://www.v4-by-example.org/), 另外, 我们也翻译了[正式版白皮书](https://mp.weixin.qq.com/s/YbEgKI0z12JLLP1EdXxCEA). 167 | 168 | 在后面的文章中, 我会介绍如何编写Hook. 以及背后的机理. 169 | 170 | -------------------------------------------------------------------------------- /gmx_v2/1_gmx介绍.md: -------------------------------------------------------------------------------- 1 | # 深入GMX V2 - 介绍 2 | 3 | 4 | 5 | ## 前言 6 | 7 | GMX 是一个去中心化交易所(DEX),专注于现货和永续合约交易,以其低费用、零滑点交易和高杠杆功能在去中心化金融(DeFi)领域占据重要地位。由于团队保持匿名,且文档较为潦草。市面很难找到详细介绍GMX V2的资源。这极大的影响了GMX相关量化策略的研究。对此,我们不得不花了一些时间对GMX V2设计以及实现进行整理,并撰写了这个系列文章。 8 | 9 | 本文面向的对象是DEFI的研究者,仅限技术讨论,不涉及任何投资建议。 10 | 11 | 由于本文内容大多通过代码整理,难免有疏漏和错误,欢迎各位读者讨论并指正。 12 | 13 | ## GMX V2介绍 14 | 15 | GMX 最初于 2021 年 9 月以 Gambit Exchange 的名称在 BNB Chain 上推出,随后更名为 GMX,并全面迁移至以太坊 Layer-2 解决方案 Arbitrum,并在 2022 年初扩展至 Avalanche 区块链。GMX 的核心目标是提供一个高效、透明且用户友好的去中心化交易平台,通过非托管的钱包交易机制,使用户能够完全掌控自己的资产。2025 年 3 月,GMX 进一步扩展至 Solana 区块链(以 GMX-Solana 的形式)。截至 2025 年,GMX 已成为 Arbitrum 和 Avalanche 生态系统中领先的衍生品交易平台,累计交易量超过 2770 亿美元,用户总数超过 72.8 万。 16 | 17 | 目前GMX仍然在迭代中,后续的计划见[这里](https://gov.gmx.io/t/gmx-v2-2-v2-3/4223)。 18 | 19 | GMX项目有4种代币: 20 | 21 | * GMX:GMX项目的治理代币 22 | * GLP:GMX V1版本的LP持仓凭证 23 | * GM:GMX V2版本的LP持仓凭证 24 | * GLV:GMX V2的进阶版本。在V2上构建了一个Vault概念。投资Vault后,可以在相同抵押品的所有池中,挑选自动挑选最优的pool投资。目前GLV有两种BTC-USDC和ETH-USDC。 25 | 26 | ### GMX V2 pool 27 | 28 | GMX V2的一大改进是将各个代币的交易池独立,这隔绝了各个代币之间的影响。交对于一个池来说,有三种代币: 29 | 30 | 1. Long token:做多代币,比如BTC,ETH 31 | 2. Short token:做空代币。基本都是USDC 32 | 3. Index token:指数代币,说白了就是永续合约要追踪的标的。 33 | 34 | 比如对于ETH/USD[ETH-USDC]池,long和index token都是ETH,short token是USDC。随着ETH价格变动,trader会有不同的PNL。 35 | 36 | 还有一种池是合成资产池。对与波动比较大的代币,GMX允许抵押相对稳定的币种。如AAVE/USD[ETH-USDC]这个池,Long token是ETH,short token是USDC,index token是AAVE。这个池用ETH代替AAVE来支持多头交易,减少抵押品本身的波动,降低资金的风险。 37 | 38 | 还有一种类型是单一币种池,long和short都是一个币种,如ETH/USD[WETH-WETH],long和short token都是WETH。 使用单一资产可以让LP以币本位做市,减少价格波动带来的损失. 39 | 40 | 另外,每个Pool都继承了ERC20合约,也就是说每个Pool就是一个token,这个Token被称作Market token,也被成为GM。LP在提供流动性后,会获得一些Market token,这些ERC20 Token也可以再次流通。而trader在开仓后,GMX不会发给trader任何凭证,只在合约中记录了仓位的信息。 41 | 42 | ### 价格 43 | 44 | 对于永续合约来说,价格是非常核心的问题,小的CEX肆意扎针让人心有余悸。而GMX价格来源使用 [Chainlink Data Stream](https://docs.chain.link/data-streams),减少价格操纵风险。这样做有两个优势: 45 | 46 | * 低延迟:价格更新频率更高(接近实时),相比GMX V1 的传统预言机,减少了价格滞后和滑点,确保交易价格更贴近市场真实价格。 47 | 48 | * 防操纵:通过多源价格聚合和 Chainlink 的去中心化验证,V2 降低了单一数据源被攻击或操纵的可能性,保障价格数据的稳定性。 49 | 50 | ### GMX V2的运行机制 51 | 52 | GMX的核心功能是永续合约交易,通俗来说,允许trader抵押一定本金,并借用一部分资金做杠杆交易,交易可以做多,也可以做空。 53 | 54 | 鉴于链上资源的限制,GMX没有采用订单簿模型,trader借用的资金,由流动性提供者(LP)来提供。在这里,LP的角色相当于CEX的做市商,与trader形成对手方。Trader的利润,就是LP的损失,反之亦然。举例来说,如果市场上多头(long)的pnl是1000u,空头(short)的pnl是-400u,那么中间这600u的空缺就要由池中所有的LP共同承担。 55 | 56 | 为此,LP承担了被动交易的风险,成为容易受损失的一方(PS:很多地方都在说成为做市商都是稳赚不赔。但实际上这种说法并不科学)。为了补偿LP,所有交易手续费,连同借款手续费,都按照一定比例分配给了LP。 57 | 同时,LP的资金也被用在为SWAP提供流动性。这与其他DEX是相似的。由于SWAP只是GMX的副业,因此GMX的swap费率极低(0.07%),相比其他DEX占很大优势。 58 | 59 | 为了平衡多空,GMX设置了funding fee机制,这也是永续合约的核心机制。funding fee在多头和空头之间转移,用于平衡多空双方。如果多头占优势,多头就要付给空头funding fee,反之空头就要给多头付funding fee。 60 | 61 | 为了平衡池中long和short代币的数量,GMX还设置了Price Impact机制。如果一笔交易,让池中的两种token变得更平衡(价值比例为1:1),那么就会得到一点奖励。如果让池中的token变得不平衡,就要付出额外的费用。比如,如果pool中BTC:USDC的比例是100:98,此时BTC多,USDC少。如果此时进行从USDC到BTC的SWAP操作,会让USDC增多,BTC减少,那么GMX就会在输出金额中增加一些额外的奖励。price impact有两种,分别是position price impact和swap price impact。 62 | 63 | ## 单词表 64 | 65 | GMX的源码中有很多术语,读懂这些术语对看懂GMX的源代码很有帮助,这里整理了一些常见的术语,并介绍他们在代码中的含义. 66 | 67 | 永续部分: 68 | 69 | * trader:进行永续交易的用户. 通过index price的变动获取收益. 70 | * long:做多 71 | * short:做空 72 | * open interest:trader的头寸,通常这个概念偏向于金额 73 | * position:trader在做永续合约交易后,所开的头寸。这个概念偏重于用户投资了这个东西,得到了这个衍生品。 74 | * increase position/decrease position:trader开杠杆或者平仓的行为. 75 | * Order:特指increase position/decrease position/swap操作的订单,与LP无关 76 | * collateral:进行合约交易时,trader抵押的资产。 77 | * funding fee:平衡多空双方的费用. 78 | * position price impact:平衡pool中两种代币的费用。 79 | * borrowing fee:trader在加杠杆后,collateral以外的资金属于向LP的借款,因此要对这部分资金支付借款手续费。 80 | * PNL:trader做永续合约交易的收入或者损失。 81 | 82 | LP部分: 83 | 84 | * LP:为池提供流动性的用户。通过获取手续费,以及做trader的对手方获取收益。 85 | * deposit:LP提供流动性行为 86 | * withdraw:LP撤出流动性的行为 87 | * pool amount:池中所有LP质押的资金的总量。 88 | * pool value:池中所有LP所能获取的价值。这个价值包括LP抵押的token,手续费收入,pnl,和position price impact。 89 | * market token amount:LP所持有凭证的数量。表示提供了多少流动性。这个token就是Pool本身。 90 | * swap price impact:平衡pool中两种代币的费用。 91 | 92 | 其他: 93 | 94 | * keeper:执行交易的服务器。 95 | * claimable fee:手续费收入有一部分(通常是37%)被划为claimable fee,这部分费用会由keeper定期收走。 96 | * capped:受限制的,有时候金额会受到一些限制,比如cappedPNL,也就是trader能够获得的PNL不能超过这个限制。 97 | * Virtual Market:在GMX V2中有虚拟市场的概念,这个概念和price impact相关,多个pool会共享一个virtual market id,形成一个虚拟市场。计算price impact的时候会比较pool和虚拟市场的平衡性,按照小的值像用户收取price impact,减少用户price impact的支出。 98 | 99 | ## 资金模型 100 | 101 | 在解析GMX V2的时候,最让人困惑的部分就是GMX pool中,资金分为多少部分,每个部分是什么关系。只有理解了这一点,才能真正了解GMX池的运作方式。我根据GMX的代码整理了一个GMX的资金模型。图中灰色部分代表整个交易池,实线代表每部分的资金,虚线是该资金来源的说明。 102 | 103 | ![GMX struct](1_gmx_fund_struct.jpg) 104 | 105 | ### Pool amount 106 | 107 | 蓝色的部分是pool amount,这个名字非常容易混淆,让人以为这是池中所有Long/Short token的数量。到区块链浏览器看一下就会发现,Pool的余额和Pool amount是对不上的。实际上Pool amount仅仅是LP对应资产,仅占池子代币余额的一部分。 108 | 109 | LP通过deposit向池内注入资金。原则上,注入的资金中,long和short的比例应该是1:1,也就是应该尽量维持long和short token平衡。如果比例不均衡甚至只抵押一个币种也可以。如果只存入long,会有一半被兑换为short,同时收取(或者发放)对应的swap price impact。 110 | 111 | 除了用户的deposit,pool amount中另一个非常重要的部分是Realized PNL。前面说过trader互为对手方,如果trader盈利,就要从Pool amount中拿走一部分钱作为利润。如果trader亏损,就会把trader的一部分collateral扣掉,并充值到Pool amount中。不过,这个"转帐"并不是时时刻刻进行的。比如trader Alice以200u的本金投资了多头,当 index price上涨,Alice得到了100u的盈利,此时这100u仅仅属于账面上的盈利,还没有落到Alice的口袋中,因此它属于Pending PNL。而且此时LP那边,Pool amount的数量也不会变化。只有在Alice关闭头寸的时候,100u的盈利才会连同200u的本金一起转入Alice的钱包。而这100u对LP形成了-100u的亏损,pool amount会扣掉100u。此时pending PNL变成了realized PNL。 112 | 113 | 需要注意,尽管pending pnl对Pool amount不会造成影响,但是对pool value还是有影响的。Pool value代表了LP的所有资产,Pending值也包括在内。 114 | 115 | | | Alice‘s position | Pool amount | Pool value | 116 | | ----------------------- | ---------------- | ----------- | ---------- | 117 | | 投入200u | 0 + 200 = 200 | 不变 | 不变 | 118 | | 价格上涨,Alice盈利100u | 200 +100 = 300 | 不变 | -100 | 119 | | Alice止盈,关闭头寸 | 300 - 300 = 0 | -100 | -100 | 120 | 121 | LP还有一块稳定的收入就是手续费,包括 122 | 123 | * Position fee:永续交易的手续费 124 | * swap fee:swap交易和LP交易的手续费 125 | * borrowing fee:永续头寸的资金占用费. 126 | 127 | 另一个比较小的部分是Realized position price impact。这部分是trader的order产生的。用于平衡pool中long/short token的数量. 128 | 129 | ### Collateral 130 | 131 | 橙色的部分是Collateral,存放的是Order的抵押资金。 这部分其实是一个虚拟的部分。Pool中只用了一个Collateral Sum记录了总的抵押量。具体每个用户有多少抵押,被记录在了每个Position对象中。 132 | 133 | 用户对头寸的操作是通过Increaes或者decrease。抵押的金额乘以杠杆,就是Open interest,比如用户抵押了5,开了10倍杠杆,Open interest就是50。合约会记录总Open Interest,这个数据很重要,计算PNL和borrowing fee都会用到。trader开仓有四种情况: 134 | 135 | * 做多(long): 136 | * 抵押long token 137 | * 抵押short token 138 | * 做空(short): 139 | * 抵押long token 140 | * 抵押short token 141 | 142 | 合约会单独记录每种情况的总Open interest。 143 | 144 | Open interest中多出来那部分钱(按照上面的例子就是50-5=45),可以看作trader是从LP那里借的,所以要支付borrowing fee。 145 | 146 | 而随着Index price的变化,用户的Open Interest会变多或者变少,这部分变化就是PNL。在用户关闭头寸之前,它是随时变化的,没有写入LP的资金池(pool amount)中,所以它处于pending的状态。 147 | 148 | ### 其它 149 | 150 | Pool还有几个独立于Collateral和Pool amount的部分。最重要的是funding fee,这部分用于平衡多空双方。占优势放需要支付funding fee,另一方则会收到funding fee。用户funding fee会根据fundingFeeAmountPerSize积累,如果如果需要支付,会统一在decrease的时候扣除。如果有盈余,则会被转入user claimable funding fee,用户需要单独发起交易领取。 151 | 152 | swap price impact是swap交易(包括LP的deposit和withdraw)相关的Price impact。 153 | 154 | claimable fee与Pool amount中的realized fee同源。所有的手续费,会有63%分给LP(也就是转入Pool amount成为realized fee),剩下的37%,则会转入到claimable fee中。keeper会定期到各个pool中收取这些手续费。注意分配的比例是可配置的,不同的池可能不同。 155 | 156 | ## 总结 157 | 158 | 这篇文章介绍了GMX的运作方式,基本概念,以及资金存放的方式。后面将结合合约源码,介绍GMX如何进行交易。 159 | -------------------------------------------------------------------------------- /uniswap-v4_address_mining.md: -------------------------------------------------------------------------------- 1 | # 限时活动!无奖励挖矿,用显卡给Uniswap v4接生 2 | 3 | 在11月10日,Uniswap官方发起了一项为[Uniswap v4挖掘地址的活动](https://blog.uniswap.org/uniswap-v4-address-mining-challenge)。 4 | 从即日起到12月1日。大家可以计算通过create2方式,计算Uniswap v4的部署地址,这些地址可以通过评价指标得到一个分数,分数最高的地址,将会作为Uniswap V4在主网的地址。简单来说,使用你的设备,进行充分的哈希计算,帮助uniswap 生成 v4的地址。 5 | 6 | ## 得分规则 7 | 这套地址的评分指标如下: 8 | 9 | * 每个前缀0得10分. 10 | * 如果前缀0之后的4位是4444,得到40分. 11 | * 如果上述的4444之后不是4,得到20分. 12 | * 如果最后4位是4444,得到20分。 13 | * 地址中每有一个4,就得1分. 14 | 15 | 举个例子,如果地址是0x00000000044442D64A0BE733A5f2a3187BFA8234,那么 16 | 17 | * 9个前缀0得到90分 18 | * 4444得到40分, 19 | * 4444之后没有4,得20分. 20 | * 地址中有6个4,得到6分。 21 | 22 | 最终可以得到156分。在这套规则下,如果想要高分,地址必须形如0x0000004444XXXXXX...确实是非常有识别度的地址了,堪比seaport的拉风地址。 23 | 24 | 另外还有个额外的要求,salt的前20位必须是提交者的地址。 25 | 26 | ## 如何开始挖矿 27 | 28 | Zelos-alpha团队也希望尝试一下。不过我们之前并没有地址挖掘的经验。毕竟,挖矿从来就不是容易的事情,不仅拼脚本,也要拼算力。我们并没有这样的技术储备。好在官方指了一条明路,用create2crunch. 29 | 30 | 我们了解了一下create2crunch,发现这个小家伙还蛮不错,它是基于rust写的。能够非常充分的利用CPU资源。但我们更看重它支持GPU计算。因为团队新入了一块GTX 4070,可以让它锻炼一下。 31 | 32 | 但是深入时候后发现create2crunch是一个目的性很强的工具,首先它假定地址都是使用factory合约部署的,因此需要传入factory地址和caller地址,这点和Uniswap的挖矿活动并不符合。另外它实现GPU计算是使用OpenCL框架, 但代码中限制只能使用默认设备。对于我们来说,这个设备是CPU而不是显卡。最严重的问题在挖矿规则上,create2crunch只支持前缀0和全部0的规则,显然这个规则太简单了,和Uniswap的规则相差很大. 33 | 34 | 在没有更好选择的情况下,我们只能fork项目,自己动手做定制化改进。 35 | 36 | 首先是地址的问题,我们研究发现,参数中的factory地址实际上是deployer地址,而caller地址实际是salt前缀。因此第一个参数应该是Uniswap提供的deployer地址, 第二个参数是自己地址, 保证salt的前缀是自己: 37 | 38 | ```sh 39 | $ cargo run --release 0x48E516B34A1274f49457b9C6182097796D0498Cb [your address] 0x94d114296a5af85c1fd2dc039cdaa32f1ed4b0fe0868f02d888bfc91feb645d9 40 | ``` 41 | 42 | 然后我们顺便更改了代码中对应的变量名,避免混淆。 43 | 44 | 下一个问题是计算平台的问题,为此我们将原来的device id参数扩充了一下,添加了platform id。这样就可以在参数中指定OpenCL的平台和设备了。以我们的工作站的OpenCL设备列表举例 45 | 46 | ```sh 47 | $ clinfo -l 48 | Platform #0: Intel(R) OpenCL 49 | `-- Device #0: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz 50 | Platform #1: NVIDIA CUDA 51 | `-- Device #0: NVIDIA GeForce RTX 4070 52 | Platform #2: Intel(R) FPGA Emulation Platform for OpenCL(TM) 53 | `-- Device #0: Intel(R) FPGA Emulation Device 54 | ``` 55 | 56 | 显卡的位置在Platform 1,device 0,因此在参数中指定1和0即可. 57 | 58 | 最终, 程序的入参如下: 59 | 60 | ```rust 61 | pub struct Config { 62 | pub deployer_address: [u8; 20],// 部署用户的地址 63 | pub salt_prefix_20: [u8; 20],// salt的前缀,本次活动中要填写自己的地址 64 | pub init_code_hash: [u8; 32],// uniswap合约的init code 65 | pub platform_id: u8,//OpenCL的platform id 66 | pub gpu_device: u8,//OpenCL的device id 67 | pub leading_zeroes_threshold: u8,//前缀0个数,最终可以得到N*2或者N*2+1个前缀0 68 | } 69 | ``` 70 | 71 | 现在是最重要的部分,规则和打分。这就需要比较多的修改了。通过分析规则我们发现,如果有N个前缀0, 并保证有4444, 形如00004444,可以得到N\*10+40分, 如果4444之后不是4(事实上很难做到)还能再拿20分。而结尾的4444可以不用管,因为如果让结尾是4444,需要保证4位是4才能得到20分(概率是1/16^4),而如果把算力放到前缀0上,只要保证2位是0就可以得到20分,概率是1/16^2。而规则"4的个数"也不用管,一个4才1分,少几个4对结果影响不大。所以,整体思路是,在保证前缀0之后是4444的情况下,得到尽可能多的前缀0。想来Uniswap制定这样的规则也很合理,毕竟多一个前缀0就能多节约一点gas。日积月累是一笔不小的数字。 72 | 73 | 现在看一下create2crunch的源代码结构,文件非常的简洁, 74 | 75 | ``` 76 | ├── kernels 77 | │ └── keccak256.cl 78 | ├── lib.rs 79 | ├── main.rs 80 | └── reward.rs 81 | ``` 82 | 83 | 其中最主要的文件是lib.rs,主要数据结构和逻辑都在这里了。 而 keccak256.cl是调用OpenCL的源代码,这部分代码并不是rust,幸运的是,这部分代码的语法很像C(应该就是C),因此即使没接触过OpenCL也应付得过来。 84 | 85 | 首先要更改的,是keccak256.cl,我们需要将判断前缀0的代码,改成判断前缀0和4444的代码,这样函数只会抛出合格的地址,避免人工再筛选。修改后就成了: 86 | 87 | ```c 88 | static inline bool hasLeading(uchar const *d) 89 | { 90 | #pragma unroll 91 | for (uint i = 0; i < LEADING_ZEROES + 1; ++i) { 92 | if(i < LEADING_ZEROES){ 93 | if (d[i] != 0) return false; 94 | }else{ 95 | if (d[i] == 68 && d[i+1]==68) return true; 96 | if (d[i] == 4 && d[i+1]==68 && ((d[i+2] & 240) == 64 )) return true; 97 | return false; 98 | } 99 | } 100 | return false; 101 | } 102 | ``` 103 | 104 | 在这段代码中,先排除前N位不是0的(注意这里的每一位d[i]的类型是uint8,在地址中占两位,比如N=3实际指000000而不是000)。 如果符合,则判断N+1和N+2是不是44,然后就可以得到形如00 00 00 44 44的地址。然后要注意一个特殊情况,就是N+1当中的第一位也是0, 此时地址形如00 00 00 04 44 4X,这就要通过位运算再判断一下N+1是不是04, N+3的高4位是不是4。 通过这样的改动,GPU就可以通过给定的N,筛选出有N*2或者N*2+1个前缀0的地址。 105 | 106 | 然后是打分, 这部分代码在lib.rs中, 107 | 108 | ```rust 109 | // count total and leading zero bytes 110 | let mut total = 0; 111 | let mut leading = 0; 112 | for (i,&b) in address.iter().enumerate() { 113 | if b & 240 == 64 { 114 | total += 1; 115 | } 116 | if b & 15 == 4 { 117 | total += 1; 118 | } 119 | if b != 0 && leading == 0 { 120 | // set leading on finding non-zero byte 121 | if b & 240 == 0 { 122 | leading = i * 2 + 1; 123 | } else { 124 | leading = i * 2; 125 | } 126 | } 127 | } 128 | let mut tail_is_4444 = 0; 129 | if address[18] == 68 && address[19] == 68 { 130 | tail_is_4444 = 1; 131 | } 132 | let mut is_not_4444_followed_by_4 = 1; 133 | if leading % 2 == 0 && (address[leading / 2 + 2] & 240 == 64) { 134 | is_not_4444_followed_by_4 = 0; 135 | } else if leading % 2 == 1 && (address[leading / 2 + 2] & 15 == 4) { 136 | is_not_4444_followed_by_4 = 0; 137 | } 138 | ``` 139 | 140 | 上面的代码做了这样几个事情: 141 | 142 | * 在统计4的总个数(total变量)时,b[i]都是地址中的两位,因此要通过位运算分别判断高位和低位是不是4. 143 | * "前缀0"(leading变量)和"4444之后不是4"(is_not_4444_followed_by_4变量)的判断和之前一样,也需要考虑前缀0是奇数个还是偶数个。 144 | * 结尾的4444(tail_is_4444变量)最好判断。 判断固定位就行. 145 | 146 | 把上面的汇总就可以得到分数 147 | 148 | ```rust 149 | let score = leading * 10 + 40 + is_not_4444_followed_by_4 * 20 + tail_is_4444 * 20 + total; 150 | ``` 151 | 152 | 程序改好后,使用显卡挖地址的效率非常惊人, 我们的GTX 4070显卡每秒可以尝试18亿次,而18核的CPU只能尝试五千万次。启动后是这个样子, 参数中的1 0 2代表使用Platfom1的Device0, 找有两组前缀0的地址(00 00)或者(00 00 0) 153 | 154 | ```sh 155 | $ cargo run --release 0x48E516B34A1274f49457b9C6182097796D0498Cb 0x88888414c16cA5793D8D239aBbab2A0e6b358568 0x94d114296a5af85c1fd2dc039cdaa32f1ed4b0fe0868f02d888bfc91feb645d9 1 0 2 156 | 157 | 158 | Setting up experimental OpenCL miner using device platform 1,device 0... 159 | total runtime: 0:00:7.069246768951416 (190 cycles) work size per cycle: 67,108,864 160 | rate: 1800.76 million attempts per second total found this run: 5 161 | current search space: 5f4b7ebcxxxxxxxx2f1f5d0e threshold: 2 leading. 162 | 0x88888414c16ca5793d8d239abbab2a0e6b358568493bc5670cdc2802810c8ae0 => 0x000044442a626dC782a8A9038d4c024703DEd20A (4 / 6) 106 163 | 0x88888414c16ca5793d8d239abbab2a0e6b358568ba7560035c942e014753d4b6 => 0x00004444bd1648b7792009cD66D33D846AecFB30 (4 / 6) 106 164 | 0x88888414c16ca5793d8d239abbab2a0e6b3585689fb0bb1ee3a3320198ea4807 => 0x00004444ED681E1a812b9eF0805e57fd78Fc50A8 (4 / 4) 104 165 | 0x88888414c16ca5793d8d239abbab2a0e6b35856865981340dbb8af03a7003c83 => 0x00004444D0852FEcCDa4D0552c0909e92801EE6e (4 / 5) 105 166 | 0x88888414c16ca5793d8d239abbab2a0e6b3585686f7109c40f4d2903b8b754aa => 0x00004444bD5DD1a99f749773Bf75128ad79858c8 (4 / 5) 105 167 | 168 | ``` 169 | 170 | 对于输出的每一行, 171 | 172 | > 0x88888414c16ca5793d8d239abbab2a0e6b358568493bc5670cdc2802810c8ae0 => 0x000044442a626dC782a8A9038d4c024703DEd20A (4 / 6) 106 173 | 174 | * 0x88888414c16ca5793d8d239abbab2a0e6b358568493bc5670cdc2802810c8ae0是地址的salt 175 | * 0x000044442a626dC782a8A9038d4c024703DEd20A 就是挖出来的地址 176 | * 4/6表示有4个前缀0, 地址中一共有6个4 177 | * 106是这个地址的打分 178 | 179 | 一切看起来都很完美,直到我们发现,即使用GPU,挖一个有8个前缀0的地址也要很久。理论上说如果要8个前缀0,就需要地址中有12位是确定的(前12位必须是00 00 00 00 44 44),计算次数是16^12=281474976710656,而我们的算力是1800.76 兆次/秒, 因此找到一个地址理论上需要1.81天,看起来还不错,不是吗? 180 | 181 | 但是我们的算力显然比不过专业玩家,经过一夜的计算,我们还没算出来地址,而网上已经有人提交9个前缀0的地址了。如果战胜它需要10个前缀0,那就是1.81 \* 16 \* 16天,算了算了不玩了。 182 | 183 | 最后我们将项目的代码开源,[https://github.com/32ethers/create2crunch](https://github.com/32ethers/create2crunch),并附上了详细的使用方法。如果你感兴趣, 并且有强大显卡。可以去挑战下。实时结果可以在[活动网站](https://v4-address.uniswap.org/)看到 。 184 | 185 | ## 挖矿奖励 186 | 给uniswap v4 接生,cool! 187 | 188 | ## 热知识:为什么要搞一个0x0000000 的地址 189 | 190 | 这实际上要追溯到eth 的黄皮书,0x000会降低gas 消耗. 191 | 能省多少的统计可以参考 https://medium.com/@solidity101/unraveling-the-gas-saving-magic-understanding-addresses-with-leading-zeros-in-solidity-smart-7a853573b116 192 | 不难推测项目方和链上的高频交易对这样的地址都有需求。比如当年的GasToken的合约地址就很多0x0开头。 193 | 此事在wintermute hack中亦有记载:https://www.forbes.com/sites/jeffkauflin/2022/09/20/profanity-may-be-the-cause-of-crypto-trading-firm-wintermutes-160-million-hack/ -------------------------------------------------------------------------------- /uniswap-v4-explore/1_pool.md: -------------------------------------------------------------------------------- 1 | # 探索Uniswap V4: 流动性池 2 | 3 | 流动性池是Uniswap的核心,也是每次升级的重头戏。如果说从V2到V3,最主要的变化是集中流动性,那么从V3到V4,最主要的变化是流动性池。在V4中,流动性池的变更主要有以下几个方面: 4 | 5 | * V3的每一个流动性池要单独部署一个合约,而在V4中所有流动性池都在PoolManager合约中。 6 | * 手续费不再仅限于几个特定值,可以自由设置手续费。 7 | * V4支持动态手续费,可以根据特定条件调整手续费比例。 8 | 9 | 下面将逐一详细介绍每个方面的改进,深入探讨它们的实现原理以及影响。 10 | 11 | ## PoolManager 12 | 13 | 在 Uniswap V3 中,流动性池是通过 UniswapV3Factory 合约部署的,每个池都是一个单独的智能合约。同时,每条链还有一个Proxy合约(这个合约在大部分链的地址是`0x364484dfb8f2185b90e29fbd10ac96fca8a7e4a7`)。在添加流动性的时候,用户需要对Proxy合约发起交易,Proxy合约再去调用Pool合约。在这个过程中,资金要先转入Proxy合约,再转入Pool变为流动性,最后Proxy合约会给用户发放一个NFT作为流动性权益的证明。可见这个过程非常繁琐,会消耗很多gas。另外,用户在交易代币时,如果使用了多跳交易,也要在不同的Pool合约中转换,同样要额外花费一些Gas。 14 | 15 | 针对这个问题,Uniswap V4引入单例模式,让所有的Pool通过PoolManager合约管理。在PoolManager有一个变量`_pools`,这个变量保存了所有池的状态。如果要新建一个Pool,只需要为`_pools`变量增加一个键值对就可以了,不需要部署合约。进行交易的时候,资金也用不着在各个合约之间划转,只要在PoolManager中做好记账就可以了。 16 | 17 | ```solidity 18 | contract PoolManager is IPoolManager,ProtocolFees,NoDelegateCall,ERC6909Claims,Extsload,Exttload { 19 | // ... 20 | mapping(PoolId id => Pool.State) internal _pools; 21 | // ... 22 | } 23 | ``` 24 | 25 | 这样做的好处很多,我们可以做一个粗糙的类比。比如我们需要进行BTC到USDC的交易,然后路由计算发现,先把BTC兑换成ETH,再把ETH兑换成USDC是最省钱的方式。因此我们的兑换路径是BTC->ETH->USDC。 26 | 对于Uniswap V3来说,它就像一个菜市场,每一个摊位都要进行一次结算,通知各个ERC20进行支付,需要分别结算BTC,ETH,USDC。 27 | 而Uniswap V4则像一个大超市,只需要在进门的时候充值,离开的时候提款就可以了,在每个柜台的交易都是挂帐,不会真的结算。只在最后结算BTC和USDC俩个资产。 28 | 29 | 在这个过程中节省的费用包含两部分: 30 | 1. 跨合约调用,在V3菜市场中每次移动摊位(pool address)都需要付费,而V4只支付一次。 31 | 2. ERC20结算费用,V4的大超市模式砍掉了全部的交易路径中的清算成本。 32 | 33 | 鉴于PoolManager合约的重要性,Uniswap还搞了个[轰轰烈烈的地址挖矿活动](https://v4-address.uniswap.org/), 为PoolManager选个吉祥地址,也能节省用户的gas。可见官方对这个合约的重视程度。 34 | 35 | 单利模式的实现,依赖于瞬态存储技术。瞬态存储是以太坊中新的数据存储方式,在EIP-1153中提出,并在2024年通过坎昆升级上线。它允许在单个交易执行期间临时存储数据,并在交易结束后自动清除。瞬态存储填补了现有存储机制的空缺,尤其适合那些数据只需在单个交易内有效且需要频繁读写的场景。下面是瞬态存储(Transient Storage)、存储(Storage)、内存(Memory)和调用数据(Calldata)的比较(引用自[瞬态存储:Solidity 中的高效临时数据解决方案](https://learnblockchain.cn/article/9847)) 36 | 37 | | 特性 | 瞬态存储 | 存储 | 内存 | 调用数据 | 38 | | :----------- | :---------------------- | :------------------------------ | :----------------- | :----------------- | 39 | | **存储位置** | 临时存储 | 永久存储 | 临时存储 | 只读存储 | 40 | | **Gas 成本** | 较低(约 100 gas/操作) | 高昂(首次写入至少 20,000 gas) | 较低 | 较低 | 41 | | **作用域** | 跨函数调用可用 | 跨交易可用 | 仅限于单个函数调用 | 仅限于函数参数 | 42 | | **状态保持** | 可以保持状态 | 永久保持状态 | 不可保持状态 | 不可保持状态 | 43 | | **清理成本** | 无需清理 | 需要额外支付 gas | 无需清理 | 无需清理 | 44 | | **大小限制** | 无明显限制 | 受链上状态限制 | 有限的内存空间 | 有限的输入参数大小 | 45 | | **适用场景** | 临时数据和计算 | 永久性数据存储 | 临时数据处理 | 函数参数传递 | 46 | 47 | 正是通过引入瞬态存储(Transient Storage)机制,Uniswap V4能够在单个交易中以极低的成本存储交易状态,而不需要将中间变量永久性地写入区块链存储。这种设计不仅降低了每次交易的gas消耗,也提升了V4的交易效率。 48 | 49 | ## 手续费 50 | 51 | 除了单例模式外,另一个巨大的变化是手续费。与V3相比,V4的流动性池可以自由设置手续费率,不再局限于几个特定的值。要直观的了解这一点,可以看一下流动性池的创建参数。在V3中,创建池的参数是这样的: 52 | 53 | > * address token0: 池的第一个token 54 | > * address token1: 池的第二个token,通常第一个和第二个根据地址排序 55 | > * uint24 fee: 池的手续费率,只能在1%,0.3%,0.05%,0.01%中选择 56 | 57 | 而在V4中,创建参数被封装成`PoolKey`对象。这个对象的字段包括: 58 | 59 | > * Currency currency0: token 0。(Currency类型是address类型的别名) 60 | > * Currency currency1: token 1。 61 | > * uint24 fee: 池的手续费率,可以设置为不超过100%的值,如果设置为0x800000,表示池使用动态手续费。 62 | > * int24 tickSpacing: 流动性的tickspacing。可以自由指定,从2到32766都可以。 63 | > * IHooks hooks: Pool的hook,想要详细了解可以查看[之前的文章](https://github.com/antalpha-labs/zelos)。 64 | 65 | 从这些参数可见,流动性池的初始化参数主要包含两部分: 代币和手续费。 66 | 67 | V4的手续费设置有两个参数,`fee`和`tickSpacing`,而V3只有`fee`。对于手续费,V3只能设置四种值(1%,0.3%,0.05%,0.01%),相比于V2只能设置0.3%,已经有了一些进步。但是在V4中,手续费率可以在小于100%的数字中任意挑选,这让池的创建由了更高的自由度,可以根据代币的特点设置更精准的手续费值。 68 | 69 | `tickSpacing`是流动性的粒度。较小的值可提高价格精度;然而,较小的值将导致swap交易更频繁地跨越tick,从而产生更高的gas成本。在V3中,tick spacing是由手续费率决定的,对应关系如下: 70 | 71 | | Fee | Fee Value | Tick Spacing | 72 | | ----- | --------- | ------------ | 73 | | 0.01% | 100 | 1 | 74 | | 0.05% | 500 | 10 | 75 | | 0.30% | 3000 | 60 | 76 | | 1.00% | 10_000 | 200 | 77 | 78 | 而V4取消了这种绑定关系,因此fee和tick spacing变成了两个参数分开设置,在V4中,tick spacing可以取从2~32766的任意整数。至于这个值取大还是取小,就要权衡价格精度以及交易频率了。比如对于稳定币来说,tick spacing要尽量小,而对于BTC这种价格接近十万的代币来说,tick spacing可以大一些以节约gas。将费率和tick spacing解绑,相当于解除了代币价格和精度的绑定关系,这会让高价格的币种收益。 79 | 80 | 这种任意设置带来了一个问题。对于相同的交易对,每个手续费,每种tick spacing都是一个新的池,因此池的数量会大大增加。比如在V3中,USDC-ETH就存在0.05%和0.3%以及1%三个池,而且三个池都很活跃,流动性排名都在前50。到了V4,由于费率有1000000种可能,V4中USDC-ETH池很容易出现上百个。要是考虑到不同的Hook也算新的池,未来的池就更多了。这必然会分散每个池的流动性。 81 | 82 | ## 动态手续费 83 | 84 | 动态手续费是V4中除Hook以外最大的变化了,意义不亚于V3的集中流动性。所谓“动态手续费”,是指流动性池的手续费不再固定为某个具体值,而是可以根据市场的具体情况随时进行调整。这种灵活性使得动态手续费能够衍生出多种使用场景,为Uniswap带来更多的创新应用和策略,从而提高整个协议的效率和适应性。以下是一些可能的动态手续费调整场景: 85 | 86 | 1. **根据当前市场价格调整** 87 | 在这种模式下,手续费的调整与市场价格的波动挂钩。例如,当某种资产的价格上涨时,手续费也相应增加,反之亦然。这种调整方式可以帮助Uniswap更好地适应市场环境的变化,尤其是在资产价格剧烈波动时,增加手续费可以有效地补偿流动性提供者的风险。 88 | 2. **根据交易量调整** 89 | 在交易量较大时,手续费可以进行折扣或者优惠。这种模式适用于大宗交易,既能吸引高频交易者来降低其交易成本,也能够鼓励流动性提供者提供更多的流动性,特别是在交易量大、价格波动小的市场中。通过优化手续费结构,可以实现市场需求与流动性提供者收益之间的平衡。 90 | 3. **根据网络gas费调整** 91 | 这一机制使得手续费与区块链网络的拥挤程度进行联动。在网络gas费较高时,流动性池可以自动提高手续费,控制流动性的变化速度;而当网络较为空闲时,手续费则可以适当降低。这种调整方式有助于流动性池的运营效率,也有助于用户在低网络费用时享受更低的交易成本。 92 | 4. **根据外部价格(如预言机)调整** 93 | 通过与外部预言机或市场数据源的结合,Uniswap能够根据准确的外部价格来动态调整手续费。这种调整机制可以确保流动性池的手续费始终与市场的实际情况相匹配,从而提供更加公平和精确的定价机制。例如,当某种资产的市场波动性增大时,手续费可以增加,以反映其风险。 94 | 5. **在特定时间调整** 95 | 这种策略允许在某些特定时间点(如每年、季度或按周)调整手续费率。这种方式适用于需要定期审视市场环境或根据季节性需求进行优化的场景。例如,每年根据上一年度的市场表现和流动性需求进行调整,以保持系统的长期健康发展。 96 | 97 | 通过上述例子可以看出,动态手续费的引入不仅为流动性池提供了更多的灵活性,也为流动性提供者和用户带来了不同的收益与节省机会。对于流动性提供者来说,动态手续费能够在市场波动较大的时候提供更高的收益,从而激励他们在高风险时期提供更多的流动性;而对于普通用户来说,手续费的动态调整能够根据交易时段、市场情况或网络拥堵程度进行优化,减少交易成本,从而提高交易体验和平台的整体吸引力。 98 | 99 | 总之,动态手续费为Uniswap引入了一种更为灵活和智能的费用管理方式,不仅增强了市场适应性,还为流动性提供者和用户提供了更多的选择和机会,推动去中心化交易协议在日益复杂的市场环境中不断向前发展。 100 | 101 | 下面的话引用了Uniswap官方的说法,描述了他们眼中动态手续费的场景: 102 | 103 | > * **改进波动性的定价**:根据市场波动性调整费用,类似于传统交易所调整买卖差价。 104 | > * **根据订单定价**:更加准确地定价不同类型的交易(例如,套利交易与非信息性交易)。 105 | > * **提升市场效率与稳定性**:费用可以根据实时市场状况进行调整,优化流动性提供者和交易者的利益。动态费用有助于通过实时调整激励措施来抑制极端市场波动。 106 | > * **提升资本效率与流动性提供者回报**:通过优化费用,池子能够吸引更多流动性并促进更高效的交易。更精准的费用定价可能带来更好的回报,进而吸引更多资本进入池子。 107 | > * **更好的风险管理**:在高波动时期,费用可以提高,以保护流动性提供者免受暂时性损失。 108 | > * **可定制的策略**:为特定代币对或市场细分提供复杂的费用策略。 109 | 110 | 动态手续费的实现依赖于流动性池的Hook。Hook是V4的新概念,它是一个单独的合约,可以为流动性池增加额外的逻辑。通过Hook,流动性池可以在交易前后插入一些自定义函数,在这些函数中都可以更改手续费率。因此,更改手续费的方式是非常灵活的,比如在添加移除流动性的时候,或者swap的前后都可以计算新的手续费,并实时更改池的手续费率 111 | 112 | 如果要允许流动性池使用动态手续费,需要在创建池的时候,将费率设置为`0x80000`, 也就是`LPFeeLibrary.DYNAMIC_FEE_FLAG` 113 | 114 | ```solidity 115 | import {PoolKey} from "v4-core/src/types/PoolKey.sol"; 116 | 117 | PoolKey memory pool = PoolKey({ 118 | currency0: currency0, 119 | currency1: currency1, 120 | fee: LPFeeLibrary.DYNAMIC_FEE_FLAG, 121 | tickSpacing: tickSpacing, 122 | hooks: hookContract 123 | }); 124 | IPoolManager(manager).initialize(pool, startingPrice); 125 | ``` 126 | 127 | 而改变动态手续费率有两种办法。一是在Hook中调用`IPoolManager.updateDynamicLPFee(key,newValue)`,永久改变池的手续费率。这个函数可以在Hook的任意挂入点调用。 比如下面这个例子: 128 | 129 | ```solidity 130 | contract HookExample { 131 | IPoolManager public immutable manager; 132 | 133 | constructor(IPoolManager _manager) { 134 | manager = _manager; 135 | } 136 | 137 | /// 在初始化池的时候设置初始的手续费 138 | function afterInitialize( 139 | address sender, 140 | PoolKey calldata key, 141 | uint160 sqrtPriceX96, 142 | int24 tick, 143 | bytes calldata hookData 144 | ) external returns (bytes4) { 145 | uint24 INITIAL_FEE = 3000; // 0.30% 146 | manager.updateDynamicLPFee(key,INITIAL_FEE); 147 | 148 | return this.afterInitialize.selector; 149 | } 150 | 151 | /// 在添加流动性之前更新手续费 152 | function beforeAddLiquidity( 153 | address, 154 | PoolKey calldata key, 155 | IPoolManager.ModifyLiquidityParams calldata, 156 | bytes calldata 157 | ) external override returns (bytes4) { 158 | // 计算得到新手续费 159 | uint24 newFee = XXX; 160 | manager.updateDynamicLPFee(key,newFee); 161 | } 162 | } 163 | ``` 164 | 165 | > 注意: 在创建池后,默认费率是0,所以要在Hook的`afterInitialize`为手续费设置一个初始值 166 | 167 | 另一种方式是临时更改费率。在Hook的`beforeSwap`中,可以返回一个费率,然后此次swap交易就会使用这个新的费率。而池的手续费率不会更改,其他的交易也不会受影响。 168 | 169 | ```solidity 170 | import {BeforeSwapDelta,BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol"; 171 | import {LPFeeLibrary} from "v4-core/src/libraries/LPFeeLibrary.sol"; 172 | 173 | contract HookExample { 174 | 175 | // ... 176 | 177 | function beforeSwap( 178 | address sender, 179 | PoolKey calldata key, 180 | IPoolManager.SwapParams calldata params, 181 | bytes calldata hookData 182 | ) external returns (bytes4,BeforeSwapDelta,uint24) { 183 | // 通过设置uint24的第二高位,设置临时手续费 184 | uint24 fee = 3000 | LPFeeLibrary.OVERRIDE_FEE_FLAG; 185 | return (this.beforeSwap.selector,BeforeSwapDeltaLibrary.ZERO_DELTA,fee); 186 | } 187 | } 188 | ``` 189 | 190 | 不过对于动态手续费,有两个概念容易混淆。在Hook中也可以额外手续一部分手续费,它和池的手续费不冲突,两个可以一起收。事实上,流动性池的手续费一般会叫做LP fee,而Hook收的手续费会叫Hook fee。一般提到动态手续费是指流动性池的手续费。 191 | 192 | ## 总结 193 | 194 | 作为领先的AMM协议,Uniswap V4的流动性池更新再次彰显了其在去中心化交易协议领域的创新与领先地位。这一系列的变革不仅增强了协议的灵活性和可扩展性,还显著提升了其市场竞争力,使得Uniswap V4在许多方面将对手甩开,继续保持在DeFi生态中的主导地位。 195 | -------------------------------------------------------------------------------- /pricing/uniswap-v3-delta-neutral.md: -------------------------------------------------------------------------------- 1 | # Uniswap V3的Delta中性策略以及优化 2 | 3 | ## 引言 4 | 5 | 在 DEFI世界中,为Uniswap交易池供流动性是一种极具吸引力的投资策略。对于每个交易池,流动性提供者只需存入两种不同的代币,即可开始赚取该池的交易手续费。这种机制不仅为流动性提供者带来了收益,也促进了整个生态系统的流动性。然而,随着代币价格的波动,流动性的净值也会随之变化,这对流动性提供者的收益产生重要影响。 6 | 7 | 下图展示了在价格变动过程中,流动性净值的变化。其中横轴为价格,纵轴为净值。彩色线则显示了在不同的 tick 范围下,流动性的净值如何随着价格的变化,而虚线则代表单纯持有某个代币的净值变化。以ETH-USDT的池为例,当价格上升的时候,LP的净值会远远落后于单纯持有ETH,仅比单纯持有USDT好一些; 而在价格下降时,损失远远大于单纯持有USDT,仅比单纯持有ETH损失少一些。 8 | 9 | ![image-20241101180629576](imgs/uniswap-v3-delta-neutral_1.png) 10 | 11 | 看起来,这实际上相当于为自己添加了0.9倍的杠杆。然而,对于风险厌恶者来说,这种波动显然是不可接受的。特别是对于市值稍小的热门代币,价格的剧烈波动可能导致净值的显著变化; 但与此同时,热门的交易又会带来丰厚的手续费收入。那么,有没有办法既能避免净值的波动,又能稳定地赚取手续费呢? 12 | 13 | ## Delta中性策略 14 | 15 | Delta中性策略可以解决这个问题。Delta是量化金融中常用的一个希腊值,含义是每当标的价格变化1个单位,衍生品的价格变化多少,用公式表示: 16 | 17 | $$ 18 | Delta = \frac {衍生品的价格变化}{标的价格变化} 19 | $$ 20 | 21 | 比如,当我们说ETH-USDT池中,一个LP的Delta为0.5,那说明当ETH价格变化为1的时候,每单位流动性变化为0.5。如下表所示,如果我们投入了相当于5 ETH的流动性。当Delta=0.5时,如果ETH上涨1u,那么每单位流动性对应增加0.5u,因此,流动性对应的净值为10002.5u。 22 | 23 | | 时间 | ETH价格 | 流动性对应净值 | 24 | | -------- | ------- | -------------------- | 25 | | 开始 | 2000 | 5 * 2000=10000 | 26 | | 价格上涨 | 2001 | 5 * 2000.5 = 10002.5 | 27 | 28 | 引申一下,如果单纯持有ETH,那么无论ETH价格变化多少,投资组合的单位净值就对应变化多少,因此单纯持有ETH的Delta是1。对应的,如果单纯持有USDT,则无论价格怎么变化,单位净值都不会发生改变。此时投资组合的Delta是0. 29 | 30 | 而借出ETH可以提供负的Delta,比如,当前ETH价格为2000u,我们借入了1 ETH,并兑换成2000u。此时我们的净值为-2000 + 2000 = 0.那么当ETH价格减少100,变为1900,我们投资组合的净值变为-1900 + 2000 = 100.所以此时的Delta是100/-100 = -1。 31 | 32 | Delta中性就是指让投资组合的Delta等于0.此时不管标的价格怎么变化,投资组合的净值都不会变。在Uniswap中,添加流动性会带来正Delta,通过借出特定量的ETH,得到负Delta,就能实现Delta中性。如果能在Uniswap中实现Delta中性,那么不仅LP的净值不会受到代币价格变化的影响,还能稳定的赚取手续费收益。即便手续费的年化收益只有8%也是很可观的,毕竟这是无风险收益。 33 | 34 | ## Uniswap V3流动性的Delta 35 | 36 | 实现Delta中性的关键,在于计算出投入流动性和借出ETH的比例。要达到这一点,首先要计算每单位流动性的Delta。在Uniswap V3中,每单位的净值可以用下面的公式计算。 37 | 38 | $$ 39 | V= 40 | \begin{cases} 41 | liq \times P(\frac{1}{\sqrt{L}}-\frac{1}{\sqrt{H}}), P < L \\ 42 | liq \times (2\sqrt{P}-\sqrt{L}-\frac{P}{\sqrt{H}}), L < P < H \\ 43 | liq \times (\sqrt{H}-\sqrt{L}), H < P 44 | \end{cases} 45 | $$ 46 | 47 | $$ 48 | Liq=\frac{1}{2-\frac{1}{\sqrt{H}}-\sqrt{L}} 49 | $$ 50 | 51 | 其中: 52 | 53 | * P是当前价格 54 | * L是添加流动性的价格下限 55 | * H是价格的上限 56 | * Liq是每单位的流动性。可以认为是1u的资金,在对应的做市范围下,相当于多少资金。 57 | 58 | 另外这里的价格是除以初始价格后的归一化价格,比如当前价格P0=2000,PH=2500,PL=1500,那么H=2500/2000=1。25,L=1500/2000=0.75,如果一段时间后当前价格PT=2100,那么P=2100/2000=1.05。 59 | 60 | Delta是衍生品价格变化的指标,相当于对价格求导。因此,通过对上式求导可以得到Uniswap V3的Delta 61 | 62 | $$ 63 | \frac{ \partial V}{\partial P} = 64 | \begin{cases} 65 | liq \times (\frac{1}{\sqrt{L}}-\frac{1}{\sqrt{H}}), P < L \\ 66 | liq \times (\frac{1}{\sqrt{P}}-\frac{1}{\sqrt{H}}), L < P < H \\ 67 | 0, H < P 68 | \end{cases} 69 | $$ 70 | 71 | 当P在价格范围内,且假定P=1(初始价格为1)的情况下,可以化简为: 72 | 73 | $$ 74 | \frac{1}{2-\frac{1}{\sqrt{H}}-\sqrt{L}} \times (\frac{1}{\sqrt{1}}-\frac{1}{\sqrt{H}}) 75 | $$ 76 | 77 | 但是要注意,Uniswap V3的Delta是一个随价格变化的复杂函数。如下图所示。因此单纯的Delta对冲无法做到净值完全不波动,只能做到在一定的价格范围内,净值波动远小于直接投资,这一点在后面的例子会有体现。 78 | 79 | ![Delta And Price](imgs/uniswap-v3-delta-neutral_2.png) 80 | 81 | ## 在Uniswap V3中实现Delta中性 82 | 83 | 有了公式,我们可以设计资金流向了。为了方便计算,我们假定初始资金为1。另外假定交易池是USDC-ETH。 84 | 85 | ```mermaid 86 | flowchart LR 87 | classDef market fill:#f96 88 | 1---->Vu1[VU1] 89 | 1-->Vu2[VU2] 90 | Vu2--supply-->AAVE([AAVE]):::market 91 | AAVE--borrow-->Veb[VEB] 92 | Veb--SWAP-->Vuc[VUC] 93 | Veb-->Vep[VEP] 94 | Vu1-->Vup[VUP] 95 | Vuc-->Vup 96 | Vep--添加流动性-->VLP([Uniswap: VLP]):::market 97 | Vup--添加流动性-->VLP 98 | ``` 99 | 100 | 图中的V表示每部分资金的价值。 101 | 102 | 1. 将初始资金按价值分为两份U1和U2。 103 | 2. 将U2抵押到AAVE。 104 | 3. 从AAVE借出ETH,价值为 $V_{EB}$ 105 | 4. 将 $V_{EB}$ 在分为两份,其中一份准备添加流动性, 106 | 5. 另一部分兑换成USDC(UC),和U1一起,组成UP,添加流动性,设置UC的目的是添加一个控制量,确保整个流程有解。 107 | 6. 使用UP和EP两部分资金添加流动性。 108 | 109 | 上面的流程必须满足一些资金关系,最显而易见的初始资金的划分关系 110 | 111 | $$ 112 | V_{U1} + V_{U2} = 1 113 | $$ 114 | 115 | 然后是借款关系,在这里,我们假定AAVE的借款系数是α,这个系数可以看作一个常数。应该小于AAVE的最大可借系数(LTV),避免因为价格波动导致清算。这里的资金价值关系应满足: 116 | 117 | $$ 118 | α \times V_{U2} = V_{EB} \\ 119 | V_{EB} = V_{UC} + V_{EP} 120 | $$ 121 | 122 | 还有一个重要的关系是流动性的关系。经过推算,在限定了做市上下界的情况下,两种代币的价值比例必须满足下面的关系: 123 | 124 | $$ 125 | \frac{V_{EP}}{V_{UP}} = \frac {1-\frac{1}{\sqrt{H}}} {1-\sqrt{L}} 126 | $$ 127 | 128 | 最后,最重要的是Delta的关系,我们在上面求出了流动性的Delta,并且知道借贷的Delta是-1,那么考虑资金的价值,可以得到下面的公式: 129 | 130 | $$ 131 | V_{LP} \times \frac{\partial V}{\partial P} - V_{EB} = 0 \\ 132 | (V_{UP} + V_{EP}) \times \frac{\partial V}{\partial P} - V_{EB} = 0 \\ 133 | $$ 134 | 135 | 将流动性的Delta展开,可以得到 136 | 137 | $$ 138 | (V_{UP} + V_{EP}) \times\frac{1}{2-\frac{1}{\sqrt{H}}-\sqrt{L}} \times (1-\frac{1}{\sqrt{H}}) - V_{EB} =0 139 | $$ 140 | 141 | 由此,将上面的公式整理一下,我们可以得到 142 | 143 | $$ 144 | \begin{cases} 145 | V_{U1} + V_{U2} = 1 \\ 146 | \frac{V_{EP}}{V_{UP}} = \frac {1-\frac{1}{\sqrt{H}}} {1-\sqrt{L}} \\ 147 | (V_{UP} + V_{EP}) \times\frac{1}{2-\frac{1}{\sqrt{H}}-\sqrt{L}} \times (1-\frac{1}{\sqrt{H}}) - V_{EB} =0 \\ 148 | V_{U1} + V_{UC} = V_{UP} \\ 149 | V_{EB} = V_{UC} + V_{EP} \\ 150 | α \times V_{U2} = V_{EB} \\ 151 | 0 < V_{U1},V_{U2},V_{EB}, V_{UC}, V_{EP},V_{UP} < 1 \\ 152 | \end{cases} 153 | $$ 154 | 155 | 在已知H和L的情况下,我们可以求出每部分的资金分配。例如,在H=1.1,L=0.9,借款系数为0.7的时候,资金分配如下: 156 | 157 | ```mermaid 158 | flowchart LR 159 | classDef market fill:#f96 160 | 1---->Vu1[0.436] 161 | 1-->Vu2[0.564] 162 | Vu2--supply-->AAVE([AAVE]):::market 163 | AAVE--borrow-->Veb[0.395] 164 | Veb--SWAP-->Vuc[0] 165 | Veb-->Vep[0.395] 166 | Vu1-->Vup[0.436] 167 | Vuc-->Vup 168 | Vep--添加流动性-->VLP([Uniswap: VLP]):::market 169 | Vup--添加流动性-->VLP 170 | ``` 171 | 172 | 随着价格变化,净值变化如下 173 | 174 | ![image-20241105165320791](imgs/uniswap-v3-delta-neutral_3.png) 175 | 176 | 在H=1.2,L=0.8,借款系数为0.75的时候,资金分配如下: 177 | 178 | ```mermaid 179 | flowchart LR 180 | classDef market fill:#f96 181 | 1---->Vu1[0.476] 182 | 1-->Vu2[0.524] 183 | Vu2--supply-->AAVE([AAVE]):::market 184 | AAVE--borrow-->Veb[0.393] 185 | Veb--SWAP-->Vuc[0] 186 | Veb-->Vep[0.393] 187 | Vu1-->Vup[0.476] 188 | Vuc-->Vup 189 | Vep--添加流动性-->VLP([Uniswap: VLP]):::market 190 | Vup--添加流动性-->VLP 191 | ``` 192 | 193 | 随着价格变化,净值变化如下 194 | 195 | ![figure3](imgs/uniswap-v3-delta-neutral_4.png) 196 | 197 | 可见随着价格变化,净值的波动被有效的控制住了,在H=1.1,L=0.9的做市区间内,当价格超出做市区间时(此时价格波动为10%),总体净值的变化仅为2%。而当H=1.2,L=0.8的做市区间内,当价格超出(波动20%),净值变化仅为5%。 198 | 199 | 这部分的代码实现,参见我们的[Github](https://github.com/zelos-alpha/delta-hedging/blob/master/math_lib_v1.py) 200 | 201 | ## 优化做市范围 202 | 203 | 现在我们已经解决了资金比例的问题。但是在我们的计算中,一直是将做市范围和借款系数作为常数。这两个参数对收益也有很大的影响。 204 | 205 | 其中借款系数并没有优化空间,只能在权衡风险后,取一个既贴近AAVE最大允许量,又不容易被清算的值。 206 | 207 | 做市范围(H和L)则不同,在Uniswap V3的世界中,如何选取做市范围一直都是困扰LP的难题,选取的过大,手续费的收益会变少。如果选取的过小,又很容易超出做市范围。 208 | 209 | 为了尽量提高手续费收益,并减少价格波动的影响,我们采取一种简单的策略,就是将做市范围和波动率挂钩。当波动率变大,使用较大的做市范围,避免频繁的超出做市范围。当波动率变小,使用较小的做市范围,获取更大的收益。用代码表示: 210 | 211 | ```python 212 | H = 1 + sigma * 上限系数 213 | L = 1 - sigma * 下限系数 214 | ``` 215 | 216 | 比如,当波动率为0.5时,如果我们采用(0.6,0.5)的参数,做市范围H=1.3,L=0.75 217 | 218 | 所以问题就转换成,如何选取合适的上限/下限系数。对于这个问题的建模,我们还在研究。这里我们采用统计的方式,看一下哪个参数组合能够获取最大的收益。具体来说,就是在一定时期内,使用Demeter对不同参数进行回测。测试哪组参数盈利最高。 219 | 220 | Demeter([https://github.com/zelos-alpha/demeter](https://github.com/zelos-alpha/demeter))是专注于DeFi以及衍生品的回测框架,支持Uniswap V3,AAVE,squeeth以及Deribit期权,通过编写回测代码,可以评估策略的表现。 221 | 222 | 利用Demeter,我们编写了一个策略([https://github.com/zelos-alpha/delta-hedging/blob/master/strategy.py](https://github.com/zelos-alpha/delta-hedging/blob/master/strategy.py)),主要思路为: 223 | 224 | 1. 根据波动率确定做市范围,然后按照上述的方式计算资金比例。(当波动率过大时,避免做市。) 225 | 2. 将一部分资金存入AAVE,然后借出ETH,最后按比例投入流动性。 226 | 3. 当价格超出做市范围或者净值(扣除手续费收益)变化达到2%时,退出当前仓位并重新做市。这样做的目的是避免价格变化过大导致投资组合的Delta与0的偏差过大。 227 | 228 | 我们在Ethereum上的usdc-eth(手续费0.05%)池进行了回测。时间范围是2024年3月1日~2024年10月16日,并测试不同参数下收益率的影响。其中行是上限参数,列是下限参数。 229 | 230 | 231 | | | 0.1 | 0.3 | 0.5 | 0.7 | 0.9 | 232 | | --- | --------- | --------- | --------- | --------- | --------- | 233 | | 0.1 | 0.007605 | -0.079808 | -0.065426 | -0.007842 | -0.024968 | 234 | | 0.3 | 0.014828 | -0.262482 | -0.076196 | 0.031028 | 0.012538 | 235 | | 0.5 | -0.165599 | -0.107021 | -0.103836 | 0.001964 | 0.045359 | 236 | | 0.7 | -0.164113 | -0.124262 | -0.087535 | 0.046455 | 0.023214 | 237 | | 0.9 | -0.163186 | -0.118111 | 0.053217 | 0.024454 | 0.016023 | 238 | 239 | 从图中可以看出,当上限为0.9,下限为0.5时,收益率最高。在这个参数下,策略的指标为: 240 | 241 | | name | value | 242 | | ---------- | ------- | 243 | | 收益 | 332.105 | 244 | | 收益率 | 0.033 | 245 | | 年化收益率 | 0.053 | 246 | | 最大回撤 | 0.037 | 247 | | 夏普比率 | 0.352 | 248 | | 波动率 | 0.066 | 249 | | Alpha | 0.074 | 250 | | Beta | 0.064 | 251 | | ETH收益 | -0.216 | 252 | | ETH收益率 | -0.321 | 253 | 254 | 收益曲线为 255 | 256 | ![image-20241105180501998](imgs/uniswap-v3-delta-neutral_5.png) 257 | 258 | 图中色块表示做市范围以及做市时间,曲线代表不同的净值。从图中可见,虽然所选的做市范围较宽,导致手续费收益不高。但是无论价格向上还是向下波动,总净值非常稳定。最终,凭借持续的手续费收益,获得了最佳的盈利。 259 | 260 | 作为对比,我们看一下最差参数(0.3,0.3)的净值曲线 261 | 262 | ![image-20241105181238722](imgs/uniswap-v3-delta-neutral_6.png) 263 | 264 | 可见,这次的调仓频率远远高于上次,由于每次调仓相当于承受2%的损失(我们的退出条件之一时净值变化超过2%),因此随着每次调仓,净值一直减少。最终造成了较大的损失。 265 | 266 | 为了求得更优化的参数,我们又在最佳参数附近,选取了精度更高的参数进行回测。最终得到如图所示: 267 | 268 | | | 0.3 | 0.4 | 0.5 | 0.6 | 0.7 | 269 | | ---- | --------- | --------- | --------- | -------- | -------- | 270 | | 0.80 | -0.119078 | -0.109294 | -0.062631 | 0.049845 | 0.044852 | 271 | | 0.85 | -0.11856 | -0.069104 | -0.062596 | 0.048965 | 0.044573 | 272 | | 0.90 | -0.118111 | -0.0696 | 0.053217 | 0.048124 | 0.024454 | 273 | | 0.95 | -0.1177 | -0.070049 | 0.052284 | 0.047364 | 0.024177 | 274 | | 1.00 | -0.076439 | -0.06983 | 0.051439 | 0.026534 | 0.023935 | 275 | 276 | 最终仍然是(0.9,0.5)胜出。从表中也可以看出,收益的变化符合一个大概的趋势。 277 | 278 | ## 总结 279 | 280 | 本文讨论了Delta中性的概念,以及如何借助AAVE借贷在Uniswap V3中实现Delta中性。然后讨论了优化做市上下界的方法。最终,通过Demeter回测,尝试出最优做市范围的参数组合。 281 | -------------------------------------------------------------------------------- /uniswap-v4-hook/2_first_hook.md: -------------------------------------------------------------------------------- 1 | # 编写第一个hook 2 | 3 | ## 引言 4 | 5 | 在去中心化金融(DeFi)的世界中,Uniswap 占据着不可或缺的地位。随着 Uniswap 的不断演进,V4 版本引入了 Hook 机制,使得开发者能够在流动性池和交易之间插入自定义的逻辑。在[上一篇文章](https://mp.weixin.qq.com/s/0-xCqBNjQmtizH-78ScA-w)中,我们已经详细介绍了 Hook 的核心概念及其作用。为了帮助大家更好地理解和应用 Hook,本篇将手把手教大家如何从头编写一个 Hook,并通过详细的代码示例和测试用例帮助您快速上手。 6 | 7 | ## 安装开发工具 8 | 9 | 编写 Uniswap V4 Hook,首先需要搭建一个开发环境。Uniswap 官方从 V3 开始便推荐使用 Foundry 进行合约开发,Foundry 是一个现代化的智能合约开发工具,基于 Rust 开发,提供编译、测试、运行、部署一站式服务。开发环境使用纯 Solidity ,不引入Javascript或者Python。 10 | 11 | Foundry 可以通过多种方式安装,官方提供了详细的安装文档: [https://book.getfoundry.sh/getting-started/installation](https://book.getfoundry.sh/getting-started/installation)。 12 | 13 | 安装完成后,就可以下一步的工作了。 14 | 15 | ## 初始化项目 16 | 17 | 安装好开发环境后,接下来我们使用 Foundry 初始化一个新的项目。初始化命令非常简单: 18 | 19 | ```bash 20 | forge init v4-hook-demo 21 | cd v4-hook-demo 22 | ``` 23 | 24 | 初始化后的项目结构非常完善,包含了 Foundry 的必要配置文件,并且自动生成了 GitHub 的 workflow 文件,这意味着如果您使用 GitHub 托管项目,已经完成了部分持续集成的配置工作。如果您希望将项目推送到 GitHub,可以按照以下步骤操作: 25 | 26 | 如果需要连接到github,可以先创建一个git项目,然后通过下面的命令同步: 27 | 28 | ```bash 29 | git remote add origin git@github.com:32ethers/v4-hook-demo.git 30 | git push --set-upstream origin master 31 | ``` 32 | 33 | 这个项目中包含一个简单的合约示例 `Counter.sol`,它演示了如何编写一个基础的智能合约。我们可以浏览一下代码,了解它的结构和功能。如果不需要的话可以将其删除: 34 | 35 | ```bash 36 | rm ./**/Counter*.sol 37 | ``` 38 | 39 | ## 安装依赖 40 | 41 | 接下来,为了使项目支持 Uniswap V4,我们需要安装相应的依赖库。以下命令将帮助我们安装核心库 `v4-core` 和外设库 `v4-periphery`: 42 | 43 | ```bash 44 | forge install Uniswap/v4-core 45 | forge install Uniswap/v4-periphery 46 | ``` 47 | 48 | 为了简化导入路径,避免在每次编写代码时都写冗长的 `import` 语句,我们可以使用 `forge remappings` 生成一个重定向文件: 49 | 50 | ```bash 51 | forge remappings > remappings.txt 52 | ``` 53 | 54 | 生成的 `remappings.txt` 文件内容如下: 55 | 56 | ```txt 57 | @ensdomains/=lib/v4-core/node_modules/@ensdomains/ 58 | @openzeppelin/=lib/v4-core/lib/openzeppelin-contracts/ 59 | @openzeppelin/contracts/=lib/v4-core/lib/openzeppelin-contracts/contracts/ 60 | @uniswap/v4-core/=lib/v4-periphery/lib/v4-core/ 61 | ds-test/=lib/v4-core/lib/forge-std/lib/ds-test/src/ 62 | erc4626-tests/=lib/v4-core/lib/openzeppelin-contracts/lib/erc4626-tests/ 63 | forge-gas-snapshot/=lib/v4-core/lib/forge-gas-snapshot/src/ 64 | forge-std/=lib/forge-std/src/ 65 | hardhat/=lib/v4-core/node_modules/hardhat/ 66 | openzeppelin-contracts/=lib/v4-core/lib/openzeppelin-contracts/ 67 | permit2/=lib/v4-periphery/lib/permit2/ 68 | solmate/=lib/v4-core/lib/solmate/ 69 | v4-core/=lib/v4-core/src/ 70 | v4-periphery/=lib/v4-periphery/ 71 | 72 | ``` 73 | 74 | 这里有一个小问题需要注意:我们需要将 `v4-core/=lib/v4-core/src/` 修改为 `v4-core/=lib/v4-periphery/lib/v4-core/src/`。这样做可以确保我们编写的 Hook 所依赖的 `v4-core` 版本与 `v4-periphery` 引用的版本保持一致,避免因版本冲突导致的编译错误。 75 | 76 | ## 设置项目环境 77 | 78 | 由于 Uniswap V4 引入了“临时存储(Transient Storage)”机制,因此我们需要指定运行环境为即将到来的坎昆硬分叉版本,并确保 Solidity 编译器版本大于 `0.8.24`。您可以在项目根目录的 `foundry.toml` 文件中添加以下三行代码来完成配置: 79 | 80 | ```toml 81 | solc_version = "0.8.26" 82 | evm_version = "cancun" 83 | ffi = true 84 | ``` 85 | 86 | 至此,环境搭建部分已经完成,接下来我们就可以开始编写 Hook 代码。 87 | 88 | ## 创建hook 89 | 90 | 在 Uniswap V4 中,Hook 的作用是允许开发者在流动性管理和交易过程中插入自定义逻辑。我们接下来将编写一个简单的 Hook,用于记录每次 `swap` 交易时的操作次数。首先,我们需要新建一个合约文件 `FirstHook.sol`, 然后添加引用。 91 | 92 | ```solidity 93 | import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol"; 94 | import {Hooks} from "v4-core/libraries/Hooks.sol"; 95 | import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol"; 96 | import {PoolKey} from "v4-core/types/PoolKey.sol"; 97 | import {PoolId,PoolIdLibrary} from "v4-core/types/PoolId.sol"; 98 | import {BalanceDelta} from "v4-core/types/BalanceDelta.sol"; 99 | import {BeforeSwapDelta,BeforeSwapDeltaLibrary} from "v4-core/types/BeforeSwapDelta.sol"; 100 | ``` 101 | 102 | 然后声明一个hook类以及类变量,并初始化构造函数。 103 | 104 | ```solidity 105 | contract CountingHook is BaseHook { 106 | using PoolIdLibrary for PoolKey; 107 | mapping(PoolId => uint256 count) public afterSwapCount; 108 | constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} 109 | } 110 | ``` 111 | 112 | 需要注意: 113 | 114 | 1. 所有的hook都继承自BaseHook。 115 | 2. Hook合约可以被多个pool引用,所以我们的类变量afterSwapCount使用了一个mapping类型,可以针对每个pool分别记录。 116 | 3. Hook合约的构造函数必须包含IPoolManager参数,IPoolManager是Hook的管理接口,能进行很多操作。 v4的库也提供了onlyByPoolManager修饰符,限制某些函数只能由管理员操作。 117 | 118 | 接下来是getHookPermissions()函数,这个函数是必须重载的。它的作用很简单,就是定义启动哪些Hook。我们想在swap交易之后,给计数器加1,所以将afterSwap设置为true,其他设置为false。 119 | 120 | ```solidity 121 | function getHookPermissions() public pure override returns (Hooks.Permissions memory) { 122 | return Hooks.Permissions({ 123 | beforeInitialize: false, 124 | afterInitialize: false, 125 | beforeAddLiquidity: false, 126 | afterAddLiquidity: false, 127 | beforeRemoveLiquidity: false, 128 | afterRemoveLiquidity: false, 129 | beforeSwap: false, 130 | afterSwap: true, 131 | beforeDonate: false, 132 | afterDonate: false, 133 | beforeSwapReturnDelta: false, 134 | afterSwapReturnDelta: false, 135 | afterAddLiquidityReturnDelta: false, 136 | afterRemoveLiquidityReturnDelta: false 137 | }); 138 | } 139 | ``` 140 | 141 | 最后,重载afterSwap函数,让计数器加一。 对于这些函数的说明,见[IHooks.sol](https://github.com/Uniswap/v4-core/blob/main/src/interfaces/IHooks.sol)。 142 | 143 | ```solidity 144 | function afterSwap(address,PoolKey calldata key,IPoolManager.SwapParams calldata,BalanceDelta,bytes calldata) 145 | external 146 | override 147 | returns (bytes4,int128) 148 | { 149 | afterSwapCount[key.toId()]++; 150 | return (BaseHook.afterSwap.selector,0); 151 | } 152 | ``` 153 | 154 | 最后贴一下完整的例子。 155 | 156 | ```solidity 157 | // SPDX-License-Identifier: MIT 158 | pragma solidity ^0.8.26; 159 | 160 | import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol"; 161 | 162 | import {Hooks} from "v4-core/libraries/Hooks.sol"; 163 | import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol"; 164 | import {PoolKey} from "v4-core/types/PoolKey.sol"; 165 | import {PoolId,PoolIdLibrary} from "v4-core/types/PoolId.sol"; 166 | import {BalanceDelta} from "v4-core/types/BalanceDelta.sol"; 167 | import {BeforeSwapDelta,BeforeSwapDeltaLibrary} from "v4-core/types/BeforeSwapDelta.sol"; 168 | 169 | contract CountingHook is BaseHook { 170 | using PoolIdLibrary for PoolKey; 171 | 172 | mapping(PoolId => uint256 count) public afterSwapCount; 173 | 174 | constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} 175 | 176 | function getHookPermissions() public pure override returns (Hooks.Permissions memory) { 177 | return Hooks.Permissions({ 178 | beforeInitialize: false, 179 | afterInitialize: false, 180 | beforeAddLiquidity: false, 181 | afterAddLiquidity: false, 182 | beforeRemoveLiquidity: false, 183 | afterRemoveLiquidity: false, 184 | beforeSwap: false, 185 | afterSwap: true, 186 | beforeDonate: false, 187 | afterDonate: false, 188 | beforeSwapReturnDelta: false, 189 | afterSwapReturnDelta: false, 190 | afterAddLiquidityReturnDelta: false, 191 | afterRemoveLiquidityReturnDelta: false 192 | }); 193 | } 194 | 195 | function afterSwap(address,PoolKey calldata key,IPoolManager.SwapParams calldata,BalanceDelta,bytes calldata) 196 | external 197 | override 198 | returns (bytes4,int128) 199 | { 200 | afterSwapCount[key.toId()]++; 201 | return (BaseHook.afterSwap.selector,0); 202 | } 203 | } 204 | 205 | ``` 206 | 207 | ## 创建测试用例 208 | 209 | 测试用例也是hook开发的重要内容。在为hook开发测试用例时,需要设置测试pool并添加流动性。这里演示如何开发测试用例。 210 | 211 | 首先添加引用。 212 | 213 | ```solidity 214 | // SPDX-License-Identifier: UNLICENSED 215 | pragma solidity ^0.8.26; 216 | 217 | import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; 218 | import {PoolSwapTest} from "v4-core/test/PoolSwapTest.sol"; 219 | import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; 220 | 221 | import {PoolManager} from "v4-core/PoolManager.sol"; 222 | import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol"; 223 | 224 | import {Currency,CurrencyLibrary} from "v4-core/types/Currency.sol"; 225 | 226 | import {Hooks} from "v4-core/libraries/Hooks.sol"; 227 | import {TickMath} from "v4-core/libraries/TickMath.sol"; 228 | import {SqrtPriceMath} from "v4-core/libraries/SqrtPriceMath.sol"; 229 | import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; 230 | 231 | import "forge-std/Test.sol"; 232 | import {CountingHook} from "../src/FirstHook.sol"; 233 | import {PoolKey} from "v4-core/types/PoolKey.sol"; 234 | import {PoolId} from "v4-core/types/PoolId.sol"; 235 | ``` 236 | 237 | 然后声明测试类,这个类需要继承Test,由于我们还需要部署虚拟类,还需要继承Deployers类,这个类中提供了很多有用的变量。 238 | 239 | ```solidity 240 | contract CountingHookTest is Test,Deployers { 241 | using CurrencyLibrary for Currency; 242 | 243 | CountingHook public hook; 244 | 245 | PoolKey pool_key; 246 | PoolId pool_id; 247 | 248 | Currency token0; 249 | Currency token1; 250 | } 251 | ``` 252 | 253 | 同时,还要声明一些类变量,包括 254 | 255 | * hook: hook对象会在setup中初始化,然后供各个测试函数调用。 256 | * pool_key,pool_id: 我们将会创建一个虚拟的pool供测试使用。 257 | * token0,token1: 这是pool的交易对所涉及的两个token。 258 | 259 | 接下来是setup()函数,它会在每个测试函数执行的时候先执行。我会将每句话的作用注释到代码中: 260 | 261 | ```solidity 262 | function setUp() public { 263 | // 部署虚拟的Manager合约和Router合约 264 | deployFreshManagerAndRouters(); 265 | 266 | // 使用内置的函数部署pool的两个token 267 | (token0,token1) = deployMintAndApprove2Currencies(); 268 | 269 | // 计算hook地址 270 | uint160 flags = uint160(Hooks.AFTER_SWAP_FLAG); 271 | address hookAddress = address(flags); 272 | 273 | // 把hook部署到指定地址上,获得hook对象 274 | deployCodeTo("FirstHook.sol",abi.encode(manager),hookAddress); 275 | hook = CountingHook(hookAddress); 276 | 277 | // 将两种token approve给hook 278 | MockERC20(Currency.unwrap(token0)).approve(address(hook),type(uint256).max); 279 | MockERC20(Currency.unwrap(token1)).approve(address(hook),type(uint256).max); 280 | 281 | // 初始化pool 282 | (pool_key,pool_id) = initPool( 283 | token0,// Currency 0 284 | token1,// Currency 1 285 | hook,// Hook对象 286 | 3000,// 设置手续费费率 287 | SQRT_PRICE_1_1,// 设置初始价格,这里相当于将价格设置为1. 288 | ZERO_BYTES // 设置Hook的初始化数据为空,这个参数会被传递给Hook的beforeInitialize和afterInitialize函数 289 | ); 290 | 291 | // 添加流动性,范围是-60 ~ 60,并将流动性设置为一个很大的数字. 292 | modifyLiquidityRouter.modifyLiquidity( 293 | pool_key, 294 | IPoolManager.ModifyLiquidityParams({ 295 | tickLower: -60, 296 | tickUpper: 60, 297 | liquidityDelta: 10 ether, 298 | salt: bytes32(0) 299 | }), 300 | ZERO_BYTES //传递给Hook的数据同样为空 301 | ); 302 | } 303 | ``` 304 | 305 | 然后是测试函数,必须以test_开头。在这个函数中,我们通过swap交易来触发Hook的执行。 306 | 307 | ```solidity 308 | function test_swap() public { 309 | // 设置swap参数 310 | IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ 311 | zeroForOne: true, // zeroForOne代表swap方向是从token0向token1 312 | amountSpecified: 0.1 ether,// swap的数量,注意这里是token数量. 313 | sqrtPriceLimitX96: MIN_PRICE_LIMIT // swap的价格限制,如果到达这个价格,交易会失败 314 | }); 315 | PoolSwapTest.TestSettings memory testSettings = 316 | PoolSwapTest.TestSettings({takeClaims: false,settleUsingBurn: false}); 317 | //检查初始值是0 318 | assertEq(hook.afterSwapCount(pool_id),0); 319 | //进行swap交易 320 | swapRouter.swap(pool_key,params,testSettings,""); 321 | //swap之后,计数器变为1 322 | assertEq(hook.afterSwapCount(pool_id),1); 323 | } 324 | 325 | 326 | ``` 327 | 328 | 完整的例子在[这里](https://github.com/32ethers/v4-hook-demo/blob/master/test/FirstHook.t.sol) 329 | 330 | 最后,格式化代码并运行测试用例。 331 | 332 | ```bash 333 | forge fmt 334 | forge test -vv 335 | ``` 336 | 337 | ## 总结 338 | 339 | 通过本文的讲解,我们已经完成了从环境搭建、依赖安装、项目初始化,到编写 Hook 和测试用例的整个流程。借助 Foundry 的便捷功能,开发 Uniswap V4 Hook 变得简单且高效。Uniswap 官方提供了非常详尽的文档和注释,帮助开发者更好地理解和使用 Hook 机制。 340 | 341 | 相关资源: 342 | 343 | - [Uniswap V4 模板项目](https://github.com/uniswapfoundation/v4-template) 344 | - [Uniswap 例子](https://www.v4-by-example.org/) 345 | - [Periphery 合约](https://github.com/Uniswap/v4-periphery) 346 | - [Core 合约](https://github.com/Uniswap/v4-core) 347 | -------------------------------------------------------------------------------- /uniswap-v2-pricing/v2-pricing.markdown: -------------------------------------------------------------------------------- 1 | # 随机过程与 uniswap v2 的定价 2 | 3 | 本文会从随机过程角度重新分析uniswap v2 lp,IL 与LVR 的重新推导。更进一步我们使用鞅停时定理对uniswap v2 LP进行分析。uniswap v2 lp 是一种奇异期权。而我们得到定价公式。 4 | 5 | 6 | ## 前置知识 7 | 8 | 我们假设你1.对uniswapV2 有了一些基础了解2.有一些随机过程尤其是伊藤引理的知识3.Shreve的《随机金融分析》中对永续美式put 定价的知识也会使用到。 9 | 10 | ## V2的基本问题陈述 11 | 12 | ### 资产价格 13 | 14 | 我们假定交易为ETH/USDC,价格服从几何布朗运动。在风险中性测量下,$S_t$遵循几何布朗运动: 15 | 16 | $$ 17 | dS_t = rS_tdt + \sigma S_tdW_t 18 | $$ 19 | 20 | 其中: 21 | 22 | - $r$是无风险利率。 23 | - $\sigma$是价格波动率。 24 | - $W_t$是标准布朗运动。 25 | 26 | 初始价格$S_0$已知。 27 | 28 | ### 定义LP价值过程 29 | 30 | 和常见的V2数据相比,我们设定我们的初始投资金额为$1。我们稍加改造可以得到 31 | 32 | $$ 33 | LP(S_0) = 1 34 | $$ 35 | 36 | $$ 37 | LP(S_t) = \sqrt{S_t} 38 | $$ 39 | 40 | 此外我们还假设我们会因为提供流动性获得手续费收入C。 41 | 42 | ### 随机过程推导 43 | 44 | 直接进行高效的随机过程分析。我们先假设投资组合$V_t$不包含手续费。 45 | 46 | $$ 47 | V_t = LP(S_t) 48 | $$ 49 | 50 | 使用伊藤引理,推导$V_t$的SDE: 51 | 52 | - $V_t = S_t^{1/2}$ 53 | - 计算偏导数: 54 | 55 | $$ 56 | \frac{\partial V}{\partial S} = \frac{1}{2} S^{-1/2}, 57 | $$ 58 | 59 | $$ 60 | \frac{\partial^2 V}{\partial S^2} = -\frac{1}{4} S^{-3/2} 61 | $$ 62 | 63 | - 应用伊藤引理: 64 | 65 | $$ 66 | dV_t = \frac{\partial V}{\partial S} dS_t + \frac{1}{2} \frac{\partial^2 V}{\partial S^2} (dS_t)^2 67 | $$ 68 | 69 | 代入$dS_t = rS_tdt + \sigma S_tdW_t$和$(dS_t)^2 = \sigma^2 S_t^2 dt$。 70 | 71 | 简化: 72 | 73 | $$ 74 | dV_t = \left[ \frac{1}{2} S^{-1/2}(rS_tdt + \sigma S_tdW_t) \right] + \frac{1}{2} \left[ -\frac{1}{4} S^{-3/2} \right] \sigma^2 S_t^2 dt 75 | $$ 76 | 77 | $$ 78 | dV_t = \frac{1}{2} S^{1/2}(rdt + \sigma dW_t) - \frac{1}{8} S^{1/2} \sigma^2 dt 79 | $$ 80 | 81 | 代入$V_t = S^{1/2}$,最终: 82 | 83 | $$ 84 | dV_t = \frac{\sigma}{2} V_tdW_t + \left( \frac{r}{2} - \frac{\sigma^2}{8} \right) V_tdt 85 | $$ 86 | 87 | 非常好,非常好。在这里我们遇到了第一个老朋友——LVR(Loss Versus Rebalancing,无常损失的新定义)。论文中给出的V2相关LVR公式如下: 88 | 89 | --- 90 | 91 | **Example 3 (Constant Product Market Maker / Uniswap v2).** Taking $\theta = 1/2$ in Example 2, we have 92 | 93 | ``` 94 | [^evans2000]: in the Black-Scholes framework, the delta of an option depends on volatility and other parameters. See also Proposition 1 of Evans [2000], evaluating a weighted geometric mean market maker over a finite horizon using risk-neutral pricing. 95 | ``` 96 | 97 | that 98 | 99 | $$ 100 | V(P) = 2L\sqrt{P}, \quad \ell(\sigma, P) = \frac{L\sigma^2}{4} \sqrt{P}, \quad \frac{\ell(\sigma, P)}{V(P)} = \frac{\sigma^2}{8}. 101 | $$ 102 | 103 | (16) 104 | 105 | 如果参考 Anthony Lee Zhang 的论文https://anthonyleezhang.github.io/pdfs/lvr.pdf 106 | 107 | 可以发现作者关注的是 LP 的瞬时损失。LVR本质上衡量的是机会成本,即"如果我将资金用于最优对冲策略会怎样"。因此,论文中通常设定 $r = 0$,只关注波动带来的损失。在我们的推导中,dt 项对应的正是 $\frac{\sigma^2}{8}$,这与 LVR 公式中的瞬时损失一致。读到这里值得停下来庆祝一下,我们通过随机分析的捷径更快地推导到了 LVR。下面我们要讨论无常损失了。 108 | 109 | ## 无常损失 110 | 111 | 无常损失在抱怨什么呢?"如果当时持有一半ETH一半USDC就好了。"这才是无常损失的真正含义。让我们用随机过程重新分析这个问题。 112 | 113 | 无常损失的正确基准应该是持有等价值的50%ETH和50%USDC的组合,其中USDC部分可以获得无风险利率$r$的收益。 114 | 115 | 接下来我们将LP价值过程的SDE与这个正确的基准进行比较: 116 | 117 | - 对于50/50 Buy & Hold策略,假设初始投资为1,则: 118 | - ETH部分:初始价值0.5,价值过程为 $0.5 \cdot S_t = 0.5 S_0 e^{(r - \sigma^2/2)t + \sigma W_t}$ 119 | - USDC部分:初始价值0.5,获得无风险收益,价值过程为 $0.5 e^{rt}$ 120 | 121 | 总价值过程为: 122 | $$ 123 | H_t = 0.5 S_0 e^{(r - \sigma^2/2)t + \sigma W_t} + 0.5 e^{rt} 124 | $$ 125 | 126 | 若 $S_0 = 1$,则: 127 | $$ 128 | H_t = 0.5 e^{(r - \sigma^2/2)t + \sigma W_t} + 0.5 e^{rt} 129 | $$ 130 | 131 | 其期望值为: 132 | $$ 133 | \mathbb{E}[H_t] = 0.5 e^{rt} + 0.5 e^{rt} = e^{rt} 134 | $$ 135 | 136 | - 对于LP策略,价值过程为: 137 | $$ 138 | V_t = \exp\left[\left(\frac{r}{2} - \frac{\sigma^2}{8}\right)t + \frac{\sigma}{2}W_t\right] 139 | $$ 140 | 141 | 其期望值为: 142 | $$ 143 | \mathbb{E}[V_t] = \exp\left\{\frac{r}{2}t\right\} 144 | $$ 145 | 146 | 现在我们可以看到,LP策略的期望收益率为$r/2$,而50/50 Buy & Hold策略的期望收益率为$r$。这正是无常损失的本质:LP的长期收益率低于等权重持有基础资产组合。 147 | 148 | 进一步考虑手续费的影响。假设手续费以常数速率$c$线性累计,则LP策略的长期平均年化收益率为: 149 | 150 | $$ 151 | \frac{r}{2} + c 152 | $$ 153 | 154 | 与50/50 Buy & Hold策略的年化收益率进行比较,得到盈亏平衡条件: 155 | 156 | $$ 157 | \frac{r}{2} + c = r 158 | $$ 159 | 160 | 即: 161 | 162 | $$ 163 | c = \frac{r}{2} 164 | $$ 165 | 166 | 也就是说,只有当单位时间手续费率$c$等于无风险利率的一半时,LP策略才能与50/50 Buy & Hold策略达到相同的期望收益率。当$c > r/2$时,LP策略才能超越基准。 167 | 168 | 值得注意的是,这个结果揭示了一个重要事实:无常损失的"损失"程度正好等于无风险利率的一半。 169 | 170 | 171 | 172 | 很好,我们已经掌握了随机过程版本的 IL 和 LVR 了。但是其实还是有一些问题没有解决。看上去这是一个衍生品,但是又没有BSM 那样的公式指导定价和 greek 分析。接下来我们将进行最革命性的推导和反直觉的结论。一切都要从永续美式期权定价开始讨论。 173 | 174 | ## 初步停时定价公式 175 | 176 | 通过观察 177 | 178 | $$ 179 | \mathbb{E} [V_t]= \exp\left\{\frac{r}{2}t\right\} 180 | $$ 181 | 182 | 你可能会以为这就是我们对 Uniswap V2 头寸价值的定价,但其实它并不是一个真正的价格公式。为什么呢?首先,它只描述了未来价值的平均增长趋势,却忽略了贴现效应——也就是说,它没有考虑“未来的一块钱在今天值多少钱”。更重要的是,它没有涉及决策:我们应该什么时候退出?是立刻赎回还是等待更久?真正的定价必须结合策略,就像期权定价中,我们关心的不是平均收益,而是如何在最佳时机行权、最大化回报。这个公式只是一个“趋势预估”,并不能告诉我们该怎么做,也无法定义当前的合理价值。 183 | 184 | 我们需要通过有决策参与的角度考虑V2头寸定价,从而得到一个完备有效的定价公式。 185 | 186 | 在 Uniswap V2 中,流动性头寸没有固定的到期日,你可以一直持有它。这就像你拥有一个“没有期限的资产”,只要你愿意,什么时候退出都可以。所以,不同于传统期权,期权越接近到期日,时间价值就越少。V2头寸在两个不同时间点 $t$ 和 $t'$下,可以认为都距离到期日有无穷多的时间,它们在时间价值上是相同的。因此,市场条件一样,即 $S_t = S_{t'}$条件下,这两个时间点的头寸从投资角度来看是一样的——没有一个比另一个更“接近到期”或“更有价值”。 V2 头寸没有这种“时间压力”,所以只看当前市场状态(比如价格),而不是挂念“还剩几天到期”。这就是为什么我们说,它没有“期限感”,更关注的是当前,而不是未来。我们能从shreve 的随机金融分析中找到永续美式期权得到很多借鉴。 187 | 188 | 因此,我们围绕以$S_t$为状态的动态规划策略,类比美式永续期权定价框架来进行接下来的工作。 189 | 190 | 我们设定预设一个价格边界 $L$,当当前价格$S_t$越过边界时立即行权。发现我们不能预知未来价格,只能根据当前价格决定是否行权,同时当前价格已经包含过去的所有信息,这完全符合一个停时(stopping time) 的定义。 191 | 192 | 遍历所有策略即所有停时的可能,得到V2头寸价值公式 193 | 194 | $$ 195 | V^*(x)= \sup_{\tau \in \mathcal{T}}\left\{\mathbb{E}\left[e^{-r\tau}\cdot\text{payoff}(\tau)\right]\right\} , \quad x=S_0 196 | $$ 197 | 198 | 若永不行权则价值为0;若立即行权则 199 | 200 | $V(S_0) = \text{payoff}(S_0) = \sqrt{S_0}$ 201 | 202 | 接下来,我们只需要找到最优停时,即找到一个最优边界 $L^*$ 即可得到一个完备的价格公式。 203 | 204 | 考虑 205 | 206 | $$ 207 | V_L(x) = \mathbb{E}\left[e^{-r\tau_L}\cdot\sqrt{S_{\tau_L}} \right] 208 | $$ 209 | 210 | 显然对于$L\leq S_0$,我们的收益总是不如单纯持有ETH,故设置 $L>S_0$,也就是一个向上停时 211 | 212 | $$ 213 | V_L(S_0) = \mathbb{E}\left[e^{-r\tau_L}\cdot\sqrt{S_{\tau_L}} \right] 214 | $$ 215 | 216 | $$ 217 | =\sqrt{L}\cdot \mathbb{E}\left[e^{-r\tau_L}\right] 218 | $$ 219 | 220 | $$ 221 | =\sqrt{L}\cdot\frac{S_0}{L} 222 | $$ 223 | 224 | $$ 225 | =\frac{S_0}{\sqrt{L}} 226 | $$ 227 | 228 | 其中$\mathbb{E}\left[e^{-r\tau_L}\right]=\frac{S_0}{L}$的计算过程见附录1 229 | 230 | 我们发现这是一个随$L$增大而减小的函数,故最佳决策是立即行权,单纯的V2头寸没有等待的价值。 231 | 232 | 回忆 $\mathbb{E} (V_t)=\exp\left\{\frac{r}{2}t\right\}$,说明LP收益不如单纯持有ETH的特点是与策略无关的。 233 | 234 | 因此我们需要改变payoff即引入手续费补偿LP来改善这一点 235 | 236 | ## 持续引入现金流模型 237 | 238 | V2内的每笔交易都会向交易者收取一定比例的手续费,手续费以留在池子内增加总储备量的形式来补偿LP。这笔伴随$S_t$价格变化产生的手续费等价于每时每刻LP都会收到一小笔现金流,速率为$C$。 239 | 240 | 我们重新建立持续收入现金流$C$的模型 241 | 242 | $$ 243 | V(S_0) = \sup_{\tau \in \mathcal{T}} \left\{ \mathbb{E} \left[ e^{-r\tau}\cdot\sqrt{S_\tau} + \int_0^\tau e^{-rt}\cdot C\,dt \right] \right\} 244 | $$ 245 | 246 | 其中 $\int_0^\tau e^{-rt}\cdot C\,dt=\frac{C}{r}(1-e^{-r\tau})$为补偿的贴现。 247 | 248 | 此时若立即行权则价值为$\sqrt{S_0}$;若永不行权则价值为 $\frac{C}{r}$。 249 | 250 | 对于 $\Delta t \to 0$ 251 | 252 | Case1: 行权 得到收益 $V(S)=\sqrt{S}$ 253 | Case2: 不行权 $V(S) \approx C\Delta t + e^{-r\Delta t} \cdot\mathbb{E} V(S_{\Delta t})$ 254 | 255 | 对于$V(S_t)$ 由伊藤引理我们有 256 | 257 | $$ 258 | dV= V'(S_t)dS_t +\frac{1}{2}V''(S_t)(dS_t)^2 259 | $$ 260 | 261 | $$ 262 | = \frac{\partial V}{\partial S}(rS_tdt+\sigma S_tdW_t)+ \frac{1}{2}\frac{\partial^2 V}{\partial S^2}\sigma^2 S_t^2 dt 263 | $$ 264 | 265 | $$ 266 | =\left(rS_tV'(S_t)+\frac{1}{2}\sigma^2 S_t^2 V''(S_t)\right)dt+\sigma S_t V'(S_t)dW_t 267 | $$ 268 | 269 | 考虑到无套利情况:$rV(S) = C+rSV'(S)+\frac{1}{2}\sigma^2 S^2 V''(S)$ 270 | 我们得到HJB方程 271 | 272 | $$ 273 | 0 = \max\left\{\sqrt{S}-V(S), \frac{1}{2}\sigma^2 S^2 V''(S)+rSV'(S)-rV(S)+C\right\} 274 | $$ 275 | 276 | 因此我们可以得到ODE 277 | 278 | $$ 279 | \frac{1}{2}\sigma^2 S^2 V'' + rSV' - rV + C = 0 280 | $$ 281 | 282 | 解得 283 | 284 | $$ 285 | V(S) = \begin{cases} 286 | \sqrt{S} & \text{当 } S \geq \left(\frac{2C}{r}\right)^2 \\ 287 | \frac{r}{4C}S + \frac{C}{r} & \text{当 } S < \left(\frac{2C}{r}\right)^2 288 | \end{cases} 289 | $$ 290 | 291 | (具体证明过程见附录2) 292 | 293 | 使用python进行蒙特卡洛模拟(详细代码见附录3) 294 | 我们得到差异值小于 $ 10^{-3} $ ,数值上验证公式正确。 295 | 296 | 观察公式,我们得到一个非常反直觉的结论,即V2头寸价值与波动率无关。 297 | 298 | 下面我们来解释为什么会有这种情况发生 299 | 根据Jensen不等式,对于凸函数$f(x)$我们有 300 | 301 | $$ 302 | f(\mathbb{E}[x]) \leq \mathbb{E}[f(x)] 303 | $$ 304 | 305 | 这意味着对于凸的payoff 函数$f(S_t)$ ,如 传统期权行权时的payoff :$f(S_t)=\max(S_t-K,0)$),其预期值的"中点"直接代入函数所得的值更高。因此引入不确定性时$\mathbb{E}[f(x)]$ 会增大。 306 | 307 | 而对于凹函数其Jensen不等式为 308 | 309 | $$ 310 | f(\mathbb{E}[x]) \geq \mathbb{E}[f(x)] 311 | $$ 312 | 313 | 这意味着对于凹的payoff函数$f(S_t)$,也就是V2中的$\sqrt{S_t}$。$\mathbb{E}[f(S_t)]$ 不会收益于波动率的升高,反而可能降低。而对于波动率惩罚的部分,由$S_t$决定的动态规划带来了最优行权边界$S^*$,也就是得到一个最优停时$\tau^*$。我们可以选择是否行权,何时($S_t$为多少时)行权,这种"择时权"可以对冲原始payoff的凹性带来的波动率惩罚。 314 | 315 | 而对于引入现金流的模型,payoff仍然是一个关于$S_t$的凹函数,并与波动率无关。$C$的参与只使得最优策略发生变化,不再是原先的立即行权,而是存在一个新的最优行权边界。 316 | 317 | 从公式的角度讲,我们在解ODE时因为会冲突边界条件而去掉了含有波动率的特征根,使得最终表达式不含波动率。 318 | 319 | 值得一提的是,虽然我们发现 V2 头寸的定价结果与波动率无关,但这其实暴露了模型的一个小缺陷:我们没有考虑建仓成本。 320 | 321 | 现实中,构建流动性头寸是需要手续费或其他成本的,尤其当波动率很高、价格剧烈波动时,LP 可能频繁达到“退出条件”并重新建仓,这些反复操作的成本会随着波动率上升而增加。比如说,每次你往池子里添加流动性,都要支付一次建仓成本(比如手续费),假设是 1 块钱。如果价格波动很小,你可能很长时间都不需要重新建仓,成本也就摊薄了。但如果波动率很高,价格经常触发退出条件,你就需要频繁重新建仓,每次都要再付那 1 块钱。这样一来,随着波动率增加,你实际花出去的总成本也会增加。但我们的模型默认你“免费”无限次建仓,这显然低估了波动率带来的实际支出。也正因如此,我们才会在数学上看到一个“与波动率无关”的结果——因为建仓这块成本完全被忽略了。这是一个值得在未来模型中修正的地方。 322 | 323 | ## 总结 324 | 我们从随机过程出发,先分析了uniswap lp 的一些基础性质,重新发现了无常损失和 LVR。然后从永续美式期权的角度出发,我们完成了对uniswap v2 的定价。 325 | 326 | 327 | 328 | | | 无常损失 | LVR | 最优停时定价 | 329 | | --- | --- | --- | --- | 330 | |机会成本 |与50/50持有ETH/USDC组合相比(USDC获得无风险利率) | 与可随时套利、自动再平衡的理想交易者相比 | 无风险利率,风险中性定价 331 | |适用场景 | 说明AMM与等权重持有基础资产的性能差异 | 用于分析LP被套利者"占便宜"的程度 |定价V2头寸,指导真实决策 | 332 | |风险度量 | 仅能粗略估算机会成本 | 可量化套利风险与损失 | 可完整计算头寸的greeks与风险敏感性 | 333 | ## 附录 334 | 335 | 1. 336 | 337 | **引理 .** 假设 $r > 0$,则有: 338 | 339 | $$ 340 | \mathbb{E}^* \left[ e^{-r\tau_L} \right] = \frac{S_0}{L}. 341 | $$ 342 | 343 | **证明.** 只需考虑 $S_0 = x < L$ 的情况。注意到对于所有 $\lambda \in \mathbb{R}$,过程 $(Z^{(N)}_t)_{t \in \mathbb{R}_+}$ 定义为: 344 | 345 | $$ 346 | Z^{(N)}_t := (S_t)^\lambda e^{-r\lambda t + \lambda \sigma^2 t/2 - \lambda^2 \sigma^2 t/2} = (S_0)^\lambda e^{\lambda \sigma W_t - \lambda^2 \sigma^2 t/2}, \quad t \geq 0, 347 | $$ 348 | 349 | 在风险中性概率测度 $\mathbb{P}^*$ 下是一个鞅。因此,停时过程 $(Z^{(N)}_{t \wedge \tau_L})_{t \in \mathbb{R}_+}$ 也是一个鞅,并且具有恒定期望,即: 350 | 351 | $$ 352 | \mathbb{E}^* \left[ Z^{(N)}_{t \wedge \tau_L} \right] = \mathbb{E}^* \left[ Z^{(N)}_0 \right] = (S_0)^\lambda, \quad t \geq 0. \quad (15.21) 353 | $$ 354 | 355 | 选择 $\lambda$ 使得: 356 | 357 | $$ 358 | r = r\lambda - \lambda \frac{\sigma^2}{2} + \lambda^2 \frac{\sigma^2}{2}, 359 | $$ 360 | 361 | 即: 362 | 363 | $$ 364 | 0 = \lambda^2 \frac{\sigma^2}{2} + \lambda \left( r - \frac{\sigma^2}{2} \right) - r = \frac{\sigma^2}{2} \left( \lambda + \frac{2r}{\sigma^2} \right) (\lambda - 1), 365 | $$ 366 | 367 | 关系式 (15.21) 可重写为: 368 | 369 | $$ 370 | \mathbb{E}^* \left[ (S_{t \wedge \tau_L})^\lambda e^{-r(t \wedge \tau_L)} \right] = (S_0)^\lambda, \quad t \geq 0. \quad (15.22) 371 | $$ 372 | 373 | 选择正解 $\lambda_+ = 1$,得到以下界限: 374 | 375 | $$ 376 | 0 \leq Z^{(N_+)}_{t} = e^{-rt} S_t \leq S_t \leq L, \quad 0 \leq t \leq \tau_L, \quad (15.23) 377 | $$ 378 | 379 | 因为 $r > 0$ 且 $S_t \leq L$ 对所有 $t \in [0, \tau_L]$ 成立。因此有 $\lim_{t \to \infty} Z^{(N_+)}_{t \wedge \tau_L} = Z^{(N_+)}_{\tau_L}$ 在 $\{\tau_L < \infty\}$ 上。由 (15.22)-(15.23) 和控制收敛定理,可得: 380 | 381 | $$ 382 | L\mathbb{E}^* \left[ e^{-r\tau_L} \right] = \mathbb{E}^* \left[ e^{-r\tau_L} S_{\tau_L} I_{\{\tau_L < \infty\}} \right] = \mathbb{E}^* \left[ \lim_{t \to \infty} e^{-r(t \wedge \tau_L)} S_{t \wedge \tau_L} \right] = \mathbb{E}^* \left[ \lim_{t \to \infty} Z^{(N_+)}_{t \wedge \tau_L} \right] = \lim_{t \to \infty} \mathbb{E}^* \left[ Z^{(N_+)}_{t \wedge \tau_L} \right] = \lim_{t \to \infty} (S_0)^\lambda = S_0, 383 | $$ 384 | 385 | 从而得到: 386 | 387 | $$ 388 | \mathbb{E}^* \left[ e^{-r\tau_L} \right] = \frac{S_0}{L}. \quad (15.24) 389 | $$ 390 | 391 | 2. 392 | 393 | 通解 $V_h = A S^{\lambda_1} + B S^{\lambda_2}$ 394 | 395 | 特解 $V_p= \frac{C}{r}$ 396 | 397 | 所以$V(S) = A S^{\lambda_1} + B S^{\lambda_2}+\frac{C}{r}$ 398 | 399 | 讨论通解,可以列出特征方程 400 | 401 | $$ 402 | \sigma^2\lambda^2 + (2r-\sigma^2)\lambda -2r =0 403 | $$ 404 | 405 | 代入 $\lambda_1=1$,发现是其中一个解 406 | 由韦达定理 $\lambda_2 = (\lambda_1+\lambda_2)-\lambda_1 = -\frac{2r-\sigma^2}{\sigma^2}-1=-\frac{2r}{\sigma^2}<0$ 407 | 408 | 讨论边界条件当$S\to 0^+$时,我们倾向于不行权,$V(S)=\frac{C}{r}$ 409 | 为了能达到此边界条件我们需要令$B=0$ 410 | 因此我们有: 411 | 412 | $$ 413 | V(S) = \begin{cases} 414 | A S + \frac{C}{r} & \text{当 } S < S^* \\ 415 | \sqrt{S} & \text{当 } S \geq S^* 416 | \end{cases} 417 | $$ 418 | 419 | 从数学角度来看,解ODE要求函数和其一阶导是连续的。 420 | 从金融角度来看, 如果一阶导数边界连接不平滑, 421 | 1当$V'(S^+)>V'(S^-)$时,等待对于$V(S)$增长没有什么帮助,LP不如早点行权拿到payoff 422 | 423 | 2当$V'(S^+)= S_star: 463 | return np.sqrt(S) 464 | else: 465 | return 0.5 * (r / (2 * C)) * S + C / r 466 | 467 | # 模拟 S(t) 路径 468 | np.random.seed(42) 469 | S = np.full(M, S0) 470 | discounted_cashflows = np.zeros(M) 471 | stopped = np.zeros(M, dtype=bool) 472 | running_discount = np.zeros(M) 473 | time = 0.0 474 | 475 | for i in range(N): 476 | if np.all(stopped): 477 | break 478 | Z = np.random.normal(size=M) 479 | S = S * np.exp((r - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * Z) 480 | time += dt 481 | 482 | # 计算每条路径是否达到停止条件 483 | trigger = (S >= S_star) & (~stopped) 484 | # 对未停止路径累加贴现手续费 485 | running_discount[~stopped] += C * np.exp(-r * time) * dt 486 | # 对达到停止条件路径,计算 sqrt(S) * e^{-r tau} 487 | discounted_cashflows[trigger] = np.sqrt(S[trigger]) * np.exp(-r * time) + running_discount[trigger] 488 | stopped[trigger] = True 489 | 490 | # 对于从未触发行权的路径(极少数),设为只收手续费 491 | discounted_cashflows[~stopped] = running_discount[~stopped] 492 | 493 | # Monte Carlo 估值 494 | V_mc = np.mean(discounted_cashflows) 495 | V_theory = V_analytical(S0) 496 | 497 | print(f"Monte Carlo Value: {V_mc:.6f}") 498 | print(f"Theoretical Value: {V_theory:.6f}") 499 | print(f"Difference: {abs(V_mc - V_theory):.6f}") 500 | ``` 501 | 502 | 503 | -------------------------------------------------------------------------------- /uniswap-v4-白皮书/readme.md: -------------------------------------------------------------------------------- 1 | # Uniswap v4 白皮书 2 | 3 | [译者](https://github.com/32ethers)注: Uniswap V4正在做发布前的准备. 白皮书也从一年前的草稿变成了正式版, 和草稿相比, 正式版对很多细节做了修改. 因此我重新翻译了一下白皮书. 把最新的版本同步给大家. 4 | 5 | 原文: [https://github.com/Uniswap/v4-core/blob/main/docs/whitepaper/whitepaper-v4.pdf](https://github.com/Uniswap/v4-core/blob/main/docs/whitepaper/whitepaper-v4.pdf) 6 | 7 | **2024年8月** 8 | 9 | 作者: 10 | 11 | * [Hayden Adams](hayden@uniswap.org) 12 | * [Moody Salem](moody.salem@gmail.com) 13 | * [Noah Zinsmeister](noah@uniswap.org) 14 | * [Sara Reynolds](sara@uniswap.org) 15 | * [Austin Adams](me@aada.ms) 16 | * [Will Pote](willpote@gmail.com) 17 | * [Mark Toda](mark@uniswap.org) 18 | * [Alice Henshaw](alice@uniswap.org) 19 | * [Emily Williams](emily@uniswap.org) 20 | * [Dan Robinson](dan@paradigm.xyz) 21 | 22 | Uniswap v4 is a non-custodial automated market maker implemented for the Ethereum Virtual Machine. Uniswap v4 offers customizability via arbitrary code hooks, allowing developers to augment the concentrated liquidity model introduced in Uniswap v3 with new functionality. In Uniswap v4, anyone can create a new pool with a specified hook, which can run before or after predetermined pool actions. Hooks can be used to implement features that were previously built into the protocol, like oracles, as well as new features that previously would have required independent implementations of the protocol. Uniswap v4 also offers improved gas efficiency and developer experience through a singleton implementation, flash accounting, and support for native ETH. 23 | 24 | Uniswap v4 是为以太坊虚拟机实现的非托管自动化做市商。Uniswap v4 通过任意代码挂钩(hooks)提供了定制化功能,允许开发者在 Uniswap v3 引入的集中流动性模型上扩展新功能。在 Uniswap v4 中,任何人都可以通过指定的挂钩创建新的资金池,该挂钩可以在预定的资金池操作之前或之后运行。挂钩可以用于实现之前内置于协议中的功能,比如预言机(oracles),以及那些以前需要独立实现协议的新功能。Uniswap v4 还通过单例实现、闪电记账(flash accounting)以及对原生 ETH 的支持,提供了改进的 gas 效率和开发者体验。 25 | 26 | 27 | ## 1 INTRODUCTION(介绍) 28 | 29 | Uniswap v4 is an automated market maker (AMM) facilitating efficient exchange of value on the Ethereum Virtual Machine (EVM). As with previous versions of the Uniswap Protocol, it is noncustodial, non-upgradable, and permissionless. The focus of Uniswap v4 is on additional customization for developers and architectural changes for gas efficiency improvements, building on the AMM model built by Uniswap v1 and v2 and the concentrated liquidity model introduced in Uniswap v3. 30 | 31 | Uniswap v4 是一种自动化做市商(AMM),用于在以太坊虚拟机(EVM)上促进高效的价值交换。与之前的 Uniswap 协议版本一样,它是非托管的、不可升级的,并且无需许可。Uniswap v4 的重点是为开发者提供更多的定制化选项,并通过架构上的改进提高 gas 效率,基于 Uniswap v1 和 v2 建立的 AMM 模型以及 Uniswap v3 引入的集中流动性模型进一步发展。 32 | 33 | Uniswap v1 [2] and v2 [3] were the first two iterations of the Uniswap Protocol, facilitating ERC-20 <> ETH and ERC-20 <> ERC-20 swaps, respectively, both using a constant product market maker (CPMM) model. Uniswap v3 [4] introduced concentrated liquidity, enabling more capital efficient liquidity through positions that provide liquidity within a limited price range, and multiple fee tiers. 34 | 35 | Uniswap v1 [2] 和 v2 [3] 是 Uniswap 协议的前两次迭代,分别促进了 ERC-20 对 ETH 和 ERC-20 对 ERC-20 的兑换,两者都使用了恒定乘积做市商(CPMM)模型。Uniswap v3 引入了集中流动性,通过在有限价格范围内提供流动性的头寸,使得资本效率更高,并且支持多个手续费等级。 36 | 37 | While concentrated liquidity and fee tiers increased flexibility for liquidity providers and allowed for new liquidity provision strategies, Uniswap v3 lacks flexibility to support new functionalities invented as AMMs and DeFi have evolved. 38 | 39 | 尽管集中流动性和手续费等级为流动性提供者增加了灵活性,并允许采用新的流动性提供策略,Uniswap v3 在支持随着 AMM 和 DeFi 发展的新功能方面仍然缺乏足够的灵活性。 40 | 41 | Some features, like the price oracle originally introduced in Uniswap v2 and included in Uniswap v3, allow integrators to utilize decentralized onchain pricing data, at the expense of increased gas costs for swappers and without customizability for integrators. Other possible enhancements, such as time-weighted average price orders (TWAP) through a time-weighted average market maker (TWAMM) [8], volatility oracles, limit orders, or dynamic fees, require reimplementations of the core protocol, and can not be added to Uniswap v3 by third-party developers. 42 | 43 | 一些功能,如最初在 Uniswap v2 中引入并包含在 Uniswap v3 中的价格预言机,允许集成者利用去中心化的链上定价数据,但代价是增加了兑换者的 gas 成本,且对集成者缺乏定制化支持。其他可能的改进,例如通过时间加权平均做市商(TWAMM)实现的时间加权平均价格订单(TWAP)、波动率预言机、限价单或动态手续费,均需要重新实现核心协议,第三方开发者无法在 Uniswap v3 中添加这些功能。 44 | 45 | Additionally, in previous versions of Uniswap, deployment of new pools involves deploying a new contract—where cost scales with the size of the bytecode—and trades with multiple Uniswap pools involve transfers and redundant state updates across multiple contracts. Additionally since Uniswap v2, Uniswap has required ETH to be wrapped into an ERC-20, rather than supporting native ETH. These design choices came with increased gas costs for end users. 46 | 47 | 此外,在之前版本的 Uniswap 中,部署新资金池需要部署一个新合约——其成本随着字节码大小的增加而增加——并且与多个 Uniswap 资金池进行交易时,会涉及多次转账和多个合约的冗余状态更新。此外,自 Uniswap v2 起,Uniswap 要求将 ETH 包装为 ERC-20,而不支持原生 ETH。这些设计选择给终端用户带来了更高的 gas 成本。 48 | 49 | In Uniswap v4, we improve on these inefficiencies through a few notable features: 50 | 51 | 在 Uniswap v4 中,我们通过以下几个显著特性改进了这些低效问题: 52 | 53 | * Hooks: Uniswap v4 allows anyone to deploy new concentrated liquidity pools with custom functionality. For each pool, the creator can define a “hook contract” that implements logic executed at specific points in a call’s lifecycle. These hooks can also manage the swap fee of the pool dynamically, implement custom curves, and adjust fees charged to liquidity providers and swappers though Custom Accounting. 54 | * Singleton: Uniswap v4 moves away from the factory model used in previous versions, instead implementing a single contract that holds all pools. The singleton model reduces the cost of pool creation and multi-hop trades. 55 | * Flash accounting: The singleton uses “flash accounting,” which allows a caller to lock the pool and access any of its tokens, as long as no tokens are owed to or from the caller by the end of the lock. This functionality is made efficient by the transient storage opcodes described in EIP-1153 [5]. Flash accounting further reduces the gas cost of trades that cross multiple pools and supports more complex integrations with Uniswap v4. 56 | * Native ETH: Uniswap v4 brings back support for native ETH, with support for pairs with native tokens inside v4 pools. ETH swappers and liquidity providers benefit from gas cost reductions from cheaper transfers and removal of additional wrapping costs. 57 | * Custom Accounting: The singleton supports both augmenting and bypassing the native concentrated liquidity pools through hook-returned deltas, utilizing the singleton as an immutable settlement layer for connected pools. This feature can support use-cases like hook withdrawal fees, wrapping assets, or constant product market maker curves like Uniswap v2. 58 | 59 | * 挂钩(Hooks): Uniswap v4 允许任何人部署具有自定义功能的新集中流动性池。对于每个池,创建者可以定义一个“挂钩合约”,该合约在调用生命周期的特定点执行逻辑。这些挂钩还可以动态管理池的兑换手续费,实现自定义曲线,并通过自定义记账(Custom Accounting)调整向流动性提供者和兑换者收取的费用。 60 | * 单例模式(Singleton): Uniswap v4 摒弃了之前版本中使用的工厂模型,改为实施一个包含所有资金池的单一合约。单例模式降低了池创建成本和多跳交易的费用。 61 | * 闪电记账(Flash Accounting): 单例合约使用“闪电记账”,允许调用者锁定池并访问其任意代币,只要在锁定结束时没有代币欠款给或从调用者处借款。此功能通过 EIP-1153 [5] 中描述的瞬态存储操作码得以高效实现。闪电记账进一步降低了跨多个资金池交易的 gas 成本,并支持与 Uniswap v4 更复杂的集成。 62 | * 原生 ETH: Uniswap v4 恢复了对原生 ETH 的支持,允许在 v4 资金池中使用包含原生代币的交易对。ETH 兑换者和流动性提供者可以从更便宜的转账和去除额外的包装成本中受益,降低 gas 成本。 63 | * 自定义记账(Custom Accounting): 单例合约支持通过挂钩返回的增量来增强和绕过原生集中流动性池,利用单例作为连接池的不可变结算层。此功能可以支持诸如挂钩提取费用、资产包装或像 Uniswap v2 那样的恒定乘积做市商曲线等用例。 64 | 65 | The following sections provide in-depth explanations of these changes and the architectural changes that help make them possible 66 | 67 | 以下章节将深入解释这些变化及使这些变化成为可能的架构调整。 68 | 69 | ## 2.HOOKS(挂钩) 70 | 71 | Hooks are externally deployed contracts that execute some developer-defined logic at a specified point in a pool’s execution. These hooks allow integrators to create a concentrated liquidity pool with flexible and customizable execution. Optionally, hooks can also return custom deltas that allow the hook to change the behavior of the swap — described in detail in the Custom Accounting section (5). 72 | 73 | 挂钩(Hooks)是外部部署的合约,在资金池执行的特定点执行一些由开发者定义的逻辑。这些挂钩允许集成者创建具有灵活和可定制执行的集中流动性池。可选地,挂钩还可以返回自定义增量,使挂钩能够改变兑换行为——在自定义记账(Custom Accounting)部分(5)中有详细描述。 74 | 75 | Hooks can modify pool parameters, or add new features and functionality. Example functionalities that could be implemented with hooks include: 76 | 77 | 挂钩可以修改池参数,或添加新的功能和特性。通过挂钩可以实现的功能示例如下: 78 | 79 | • Executing large orders over time through TWAMM [8] 80 | • Onchain limit orders that fill at tick prices 81 | • Volatility-shifting dynamic fees 82 | • Mechanisms to internalize MEV for liquidity providers [1] 83 | • Median, truncated, or other custom oracle implementations 84 | • Constant Product Market Makers (Uniswap v2 functionality) 85 | 86 | * 通过 TWAMM [8] 执行长期大额订单 87 | * 在价格刻度处填充的链上限价单 88 | * 波动率变化的动态费用 89 | * 为流动性提供者内化MEV的机制[1] 90 | * 中位数、截断或其他自定义预言机实现 91 | * 恒定产品市场做市商(Uniswap V2的功能) 92 | 93 | 94 | ### 2.1 Action Hooks(操作的挂钩) 95 | 96 | When someone creates a pool on Uniswap v4, they can specify a hook contract. This hook contract implements custom logic that the pool will call out to during its execution. Uniswap v4 currently supports ten such hook callbacks: 97 | 98 | 当有人在 Uniswap v4 上创建一个资金池时,他们可以指定一个挂钩合约。这个挂钩合约实现了自定义逻辑,在资金池执行过程中会被调用。Uniswap v4 目前支持以下十种挂钩回调: 99 | 100 | • beforeInitialize/afterInitialize 101 | • beforeAddLiquidity/afterAddLiquidity 102 | • beforeRemoveLiquidity/afterRemoveLiquidity 103 | • beforeSwap/afterSwap 104 | • beforeDonate/afterDonate 105 | 106 | The address of the hook contract determines which of these hook callbacks are executed. This creates a gas efficient and expressive methodology for determining the desired callbacks to execute, and ensures that even upgradeable hooks obey certain invariants. There are minimal requirements for creating a working hook. In Figure 1, we describe how the beforeSwap and afterSwap hooks work as part of swap execution flow. 107 | 108 | 挂钩合约的地址决定了哪些挂钩回调被执行。这种方式为确定所需的回调提供了一种高效且灵活的解决方案,同时确保即使是可升级的挂钩也遵守某些不变量。创建一个可运行的挂钩的要求非常低。在图 1 中,我们描述了 `beforeSwap` 和 `afterSwap` 挂钩如何作为兑换执行流程的一部分工作。 109 | 110 | ![钩子图](/imgs/uniswap-v4_1.png) 111 | 112 | ### 2.2 Hook-managed fees(通过挂钩管理手续费) 113 | 114 | Uniswap v4 allows fees to be taken on swapping by the hook. Swap fees can be either static, or dynamically managed by a hook contract. The hook contract can also choose to allocate a percentage of the swap fees to itself. Fees that accrue to hook contracts can be allocated arbitrarily by the hook’s code, including to liquidity providers, swappers, hook creators, or any other party. 115 | 116 | Uniswap v4 允许通过挂钩收取兑换手续费。兑换手续费可以是静态的,也可以由挂钩合约动态管理。挂钩合约还可以选择将一部分兑换手续费分配给自己。挂钩合约累积的费用可以由挂钩的代码任意分配,包括分配给流动性提供者、兑换者、挂钩创建者或任何其他方。 117 | 118 | The capabilities of the hook are limited by immutable flags chosen when the pool is created. For example, a pool creator can choose whether a pool has a static fee (and what that fee is) or dynamic fees. 119 | 120 | 挂钩的功能受限于在创建池时选择的不可变标志。例如,池创建者可以选择池是否有静态费用(以及费用的具体数额)或动态费用。 121 | 122 | Governance also can take a capped percentage of swap fees, as discussed below in the Governance section (6.2). 123 | 124 | 治理还可以收取一个封顶的兑换手续费百分比,具体内容在治理部分(6.2)中讨论。 125 | 126 | ## 3 SINGLETON AND FLASH ACCOUNTING(单利和闪电记账) 127 | 128 | Previous versions of the Uniswap Protocol use the factory/pool pattern, where the factory creates separate contracts for new token pairs. Uniswap v4 uses a singleton design pattern where all pools are managed by a single contract, making pool deployment 99% cheaper. 129 | 130 | 以前版本的 Uniswap 协议使用工厂/池模式,工厂为新的代币对创建单独的合约。Uniswap v4 使用单例设计模式,其中所有资金池由一个合约管理,这使得池的部署成本降低了 99%。 131 | 132 | The singleton design complements another architectural change in v4: flash accounting. In previous versions of the Uniswap Protocol, most operations (such as swapping or adding liquidity to a pool) ended by transferring tokens. In v4, each operation updates an internal net balance, known as a delta, only making external transfers at the end of the lock. The new take() and settle() functions can be used to borrow or deposit funds to the pool, respectively. By requiring that no tokens are owed to the pool manager or to the caller by the end of the call, the pool’s solvency is enforced. 133 | 134 | 单例设计与 v4 中另一个架构变化——闪电记账(flash accounting)互补。在之前版本的 Uniswap 协议中,大多数操作(如兑换或向池中添加流动性)都以转账代币的方式结束。而在 v4 中,每个操作只更新一个内部净余额,称为增量(delta),仅在锁定结束时进行外部转账。新的 take() 和 settle() 函数可以分别用于借入或存入资金到池中。通过要求在调用结束时池管理者或调用者不欠任何代币,从而强制执行池的偿付能力。 135 | 136 | Flash accounting simplifies complex pool operations, such as atomic swapping and adding. When combined with the singleton model, it also simplifies multi-hop trades or compound operations like swapping before adding liquidity. 137 | 138 | 闪电记账简化了复杂的池操作,如原子化的兑换和添加(流动性)。当与单例模型结合使用时,它还简化了多跳交易或复合操作(如在添加流动性之前兑换)。 139 | 140 | Before the Cancun hard fork, the flash accounting architecture was expensive because it required storage updates at every balance change. Even though the contract guaranteed that internal accounting data is never actually serialized to storage, users would still pay those same costs once the storage refund cap was exceeded [6]. But, because balances must be 0 by the end of the transaction, accounting for these balances can be implemented with transient storage, as specified by EIP-1153 [5]. 141 | 142 | 在 Cancun 硬分叉之前,闪电记账架构成本较高,因为它需要在每次余额变更时更新存储。尽管合约保证内部记账数据不会实际序列化到存储中,但一旦超出存储退款上限,用户仍需支付相同的成本 [6]。然而,由于余额必须在交易结束时为 0,因此这些余额的记账可以通过瞬态存储来实现,如 EIP-1153 [5] 所规定的。 143 | 144 | Together, singleton and flash accounting enable more efficient routing across multiple v4 pools, reducing the cost of liquidity fragmentation. This is especially useful given the introduction of hooks, which will greatly increase the number of pools. 145 | 146 | 单例模式和闪电记账共同使得跨多个 v4 池的路由更加高效,减少了流动性碎片化的成本。鉴于挂钩的引入,这尤其有用,因为它将大大增加池的数量。 147 | 148 | 149 | ## 4 NATIVE ETH(原生ETH) 150 | 151 | Uniswap v4 is bringing back native ETH in trading pairs. While Uniswap v1 was strictly ETH paired against ERC-20 tokens, native ETH pairs were removed in Uniswap v2 due to implementation complexity and concerns of liquidity fragmentation across WETH and ETH pairs. Singleton and flash accounting mitigate these problems, so Uniswap v4 allows for both WETH and ETH pairs. Native ETH transfers are about half the gas cost of ERC-20 transfers (21k gas for ETH and around 40k gas for ERC-20s). Currently Uniswap v2 and v3 require the vast majority of users to wrap (unwrap) their ETH to (from) WETH before (after) trading on the Uniswap Protocol, requiring extra gas. According to transaction data, the majority of users start or end their transactions in ETH, adding this additional unneeded complexity. 152 | 153 | Uniswap v4 恢复了在交易对中使用原生 ETH。虽然 Uniswap v1 中严格使用 ETH 与 ERC-20 代币配对,但由于实现复杂性和 WETH 与 ETH 配对之间的流动性碎片化问题,Uniswap v2 移除了原生 ETH 配对。单例模式和闪电记账缓解了这些问题,因此 Uniswap v4 允许同时支持 WETH 和 ETH 配对。原生 ETH 转账的 gas 成本大约是 ERC-20 转账的一半(ETH 的 gas 成本为 21k,而 ERC-20 为约 40k)。目前,Uniswap v2 和 v3 要求绝大多数用户在 Uniswap 协议上交易前(后)将 ETH 包装(解包装)为 WETH,这增加了额外的 gas 成本。根据交易数据,大多数用户开始或结束他们的交易时使用 ETH,这增加了不必要的复杂性。 154 | 155 | ## 5 CUSTOM ACCOUNTING(自定义记账) 156 | 157 | Newly introduced in Uniswap v4 is custom accounting which allows hook developers to alter end user actions utilizing hook-returned deltas, token amounts that are debited/credited to the user and credited/debited to the hook, respectively. This allows hook developers to potentially add withdrawal fees on LP positions, customized LP fee models, or match against some flow, all while ultimately utilizing the internal concentrated liquidity native to Uniswap v4. 158 | 159 | 在 Uniswap v4 中引入的新功能是自定义记账(custom accounting),允许挂钩开发者通过挂钩返回的增量(hook-returned deltas)来调整终端用户的操作,这些增量分别表示从用户账户借记/贷记的代币数量和贷记/借记到挂钩的代币数量。这使得挂钩开发者能够为流动性提供者(LP)位置添加提款费用、定制 LP 收费模型,或与某些流动性匹配,同时最终利用 Uniswap v4 原生的集中流动性。 160 | 161 | Importantly, hook developers can also forgo the concentrated liquidity model entirely, creating custom curves from the v4 swap parameters. This creates interface composability for integrators allowing the hook to map the swap parameters to their internal logic. 162 | 163 | 重要的是,挂钩开发者还可以完全绕过集中流动性模型,从 v4 兑换参数创建自定义曲线。这为集成者提供了接口组合性,允许挂钩将兑换参数映射到其内部逻辑。 164 | 165 | In Uniswap v3, users were required to utilize the concentrated liquidity AMM introduced in the same version. Since their introduction, concentrated liquidity AMMs have become widely used as the base liquidity provision strategy in the decentralized finance markets. While concentrated liquidity is able to support most arbitrary liquidity provision strategies, it may require increased gas overhead to implement specific strategies. 166 | 167 | 在 Uniswap v3 中,用户被要求利用该版本引入的集中流动性 AMM。自其引入以来,集中流动性 AMM 已成为去中心化金融市场中的基础流动性提供策略。尽管集中流动性可以支持大多数任意的流动性提供策略,但实施特定策略可能需要增加 gas 开销。 168 | 169 | One possible example is a Uniswap v2 on Uniswap v4 hook, which bypasses the internal concentrated liquidity model entirely utilizing a constant product market maker fully inside of the hook. Using custom accounting is cheaper than creating a similar strategy in the concentrated liquidity math. 170 | 171 | 一个可能的例子是在 Uniswap v4 中使用的 Uniswap v2 挂钩,它完全绕过了内部集中流动性模型,利用挂钩内部的恒定乘积做市商。使用自定义记账比在集中流动性数学中创建类似策略更便宜。 172 | 173 | The benefit of custom accounting for developers(compared to rolling a custom AMM) is the singleton, flash accounting, and ERC-6909. These features support cheaper multi-hop swaps, security benefits, and easier integration for flow. Developers should also benefit from a well-audited code-base for the basis of their AMM. 174 | 175 | 对于开发者来说,自定义记账的好处(与创建自定义 AMM 相比)包括单例模式、闪电记账和 ERC-6909。这些特性支持更便宜的多跳兑换、安全性益处和更容易的流动性集成。开发者还可以从经过良好审计的代码库中受益,作为他们 AMM 的基础。 176 | 177 | Custom accounting will also support experimentation in liquidity provision strategies, which historically requires the creation of an entirely new AMM. Creating a custom AMM requires significant technical resources and investment, which may not be economically viable for many. 178 | 179 | 自定义记账还将支持流动性提供策略的实验,这在历史上需要创建一个全新的 AMM。创建自定义 AMM 需要大量的技术资源和投资,对于许多开发者来说,经济上可能不可行。 180 | 181 | ## 6 OTHER NOTABLE FEATURES(其他功能) 182 | 183 | ### 6.1 ERC-6909 Accounting(ERC-6909记账) 184 | 185 | Uniswap v4 supports the minting/burning of singleton-implemented ERC-6909 tokens for additional token accounting, described in the ERC-6909 specification [7]. Users can now keep tokens within the singleton and avoid ERC-20 transfers to and from the contract. This will be especially valuable for users and hooks who continually use the same tokens over multiple blocks or transactions, like frequent swappers, liquidity providers, or custom accounting hooks. 186 | 187 | Uniswap v4 支持铸造/销毁单例实现的 ERC-6909 代币,以进行额外的代币记账,具体描述见 ERC-6909 规范 [7]。用户现在可以将代币保留在单例合约中,避免了 ERC-20 转账进出合约。这对那些在多个区块或交易中持续使用相同代币的用户和挂钩尤为重要,例如频繁的兑换者、流动性提供者或自定义记账挂钩。 188 | 189 | ### 6.2 Governance updates(治理机制升级) 190 | 191 | Similar to Uniswap v3, Uniswap v4 allows governance the ability to take up to a capped percentage of the swap fee on a particular pool, which are additive to LP fees. Unlike in Uniswap v3, governance does not control the permissible fee tiers or tick spacings. 192 | 193 | 与 Uniswap v3 类似,Uniswap v4 允许治理机制从特定资金池的交换手续费中收取一定的封顶百分比,这些费用是对流动性提供者费用的额外补充。但不同于 Uniswap v3,治理机制不再控制允许的手续费等级或价格刻度间隔。 194 | 195 | ### 6.3 Gas reductions(减少gas使用) 196 | 197 | As discussed above, Uniswap v4 introduces meaningful gas optimizations through flash accounting, the singleton model, and support for native ETH. Additionally, the introduction of hooks makes the protocol-enshrined price oracle that was included in Uniswap v2 and Uniswap v3 unnecessary, which also means base pools forgo the oracle altogether and save around 15k gas on the first swap on a pool in each block. 198 | 199 | 如前所述,Uniswap v4 通过闪电记账、单例模型和对原生 ETH 的支持引入了重要的 gas 优化。此外,挂钩的引入使得 Uniswap v2 和 Uniswap v3 中包含的协议内置价格预言机变得不再必要,这也意味着基础资金池完全舍弃了预言机,每个区块内池的第一次兑换可节省约 15k gas。 200 | 201 | ### 6.4 donate() (捐赠函数) 202 | donate() allows users, integrators, and hooks to directly pay in-range liquidity providers in either or both of the tokens of the pool. This functionality relies on the fee accounting system to facilitate efficient payments. The fee payment system can only support either of the tokens in the token pair for the pool. Potential use-cases could be tipping in-range liquidity providers on TWAMM orders or new types of fee systems. 203 | 204 | donate()允许用户、集成者和挂钩直接向处于范围内的流动性提供者支付池中的一个或两个代币。这一功能依赖于费用记账系统以实现高效的支付。费用支付系统只能支持池中代币对的其中一个代币。潜在的使用场景包括对 TWAMM 订单中的范围内流动性提供者进行小费,或新型费用系统的实现。 205 | 206 | ## 7 SUMMARY(总结) 207 | 208 | In summary, Uniswap v4 is a non-custodial, non-upgradeable, and permissionless AMM protocol. It builds upon the concentrated liquidity model introduced in Uniswap v3 with customizable pools through hooks. Complementary to hooks are other architectural changes like the singleton contract which holds all pool state in one contract, and flash accounting which enforces pool solvency across each pool efficiently. Additionally, hook developers can elect to bypass the concentrated liquidity entirely, utilizing the v4 singleton as an arbitrary delta resolver. Some other improvements are native ETH support, ERC-6909 balance accounting, new fee mechanisms, and the ability to donate to in-range liquidity providers. 209 | 210 | 总而言之,Uniswap v4 是一个非托管、不可升级且无需许可的 AMM 协议。它在 Uniswap v3 引入的集中流动性模型基础上,通过挂钩实现了可定制的资金池。与挂钩互补的还有其他架构变化,如单例合约,它将所有资金池状态集中在一个合约中,以及闪电记账,它高效地强制执行每个池的偿付能力。此外,挂钩开发者可以选择完全绕过集中流动性,利用 v4 单例作为任意增量解析器。其他改进包括对原生 ETH 的支持、ERC-6909 余额记账、新的费用机制,以及向范围内流动性提供者捐赠的能力。 211 | 212 | ## REFERENCES(引用) 213 | [1] Austin Adams, Ciamac Moallemi, Sara Reynolds, and Dan Robinson. 2024. am-AMM: An Auction-Managed Automated Market Maker. arXiv preprint arXiv:2403.03367 (2024). 214 | [2] Hayden Adams. 2018. Uniswap v1 Core. Retrieved Jun 12, 2023 from https://hackmd.io/@HaydenAdams/HJ9jLsfTz 215 | [3] Hayden Adams, Noah Zinsmeister, and Dan Robinson. 2020. Uniswap v2 Core. Retrieved Jun 12, 2023 from https://uniswap.org/whitepaper.pdf 216 | [4] Hayden Adams, Noah Zinsmeister, Moody Salem, River Keefer, and Dan Robinson. 2021. Uniswap v3 Core. Retrieved Jun 12, 2023 from https://uniswap.org/whitepaper-v3.pdf 217 | [5] Alexey Akhunov and Moody Salem. 2018. EIP-1153: Transient storage opcodes. Retrieved Jun 12, 2023 from https://eips.ethereum.org/EIPS/eip-1153 218 | [6] Vitalik Buterin and Martin Swende. 2021. EIP-3529: Reduction in refunds. Retrieved Jun 12, 2023 from https://eips.ethereum.org/EIPS/eip-3529 219 | [7] JT Riley, Dillon, Sara, Vectorized, and Neodaoist. 2023. ERC-6909: Minimal MultiToken Interface. Retrieved Aug 26, 2024 from https://eips.ethereum.org/EIPS/eip-6909 220 | [8] Dave White, Dan Robinson, and Hayden Adams. 2021. TWAMM. Retrieved Jun 12, 2023 from https://www.paradigm.xyz/2021/07/twamm 221 | 222 | 223 | ## DISCLAIMER(免责声明) 224 | This paper is for general information purposes only. It does not constitute investment advice or a recommendation or solicitation to buy or sell any investment and should not be used in the evaluation of the merits of making any investment decision. It should not be relied upon for accounting, legal or tax advice or investment recommendations. This paper reflects current opinions of the authors and is not made on behalf of Uniswap Labs, Paradigm, or their affiliates and does not necessarily reflect the opinions of Uniswap Labs, Paradigm, their affiliates or individuals associated with them. The opinions reflected herein are subject to change without being updated. 225 | 226 | 本文仅供一般信息参考之用,不构成投资建议,也不构成购买或出售任何投资的推荐或引导,且不应作为评估任何投资决策优劣的依据。本文不应被用作会计、法律或税务建议或投资推荐的依据。本文反映了作者的当前观点,并非代表 Uniswap Labs、Paradigm 或其关联公司发表,且不一定反映 Uniswap Labs、Paradigm、其关联公司或与其相关人员的观点。本文所述观点可能会发生变化,且不会进行更新。 227 | 228 | 229 | 230 | 231 | 232 | --------------------------------------------------------------------------------