From abb4e00522384bc32b3684b2c75ce530f24c6807 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Thu, 18 Apr 2024 13:00:06 -0400 Subject: [PATCH 01/14] forge install: pendle-core-v2-public --- .gitmodules | 3 +++ lib/pendle-core-v2-public | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/pendle-core-v2-public diff --git a/.gitmodules b/.gitmodules index 7d5592b0..b13a0d61 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,3 +6,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/pendle-core-v2-public"] + path = lib/pendle-core-v2-public + url = https://github.com/pendle-finance/pendle-core-v2-public diff --git a/lib/pendle-core-v2-public b/lib/pendle-core-v2-public new file mode 160000 index 00000000..bc27b10c --- /dev/null +++ b/lib/pendle-core-v2-public @@ -0,0 +1 @@ +Subproject commit bc27b10c33ac16d6e1936a9ddd24d536b00c96a4 From bc1344aec5f7fd1eadbf1b67ac6978f18a259e0e Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Thu, 18 Apr 2024 13:00:18 -0400 Subject: [PATCH 02/14] forge install: openzeppelin-contracts v5.0.2 --- .gitmodules | 3 +++ lib/openzeppelin-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index b13a0d61..cce1f0e6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,3 +9,6 @@ [submodule "lib/pendle-core-v2-public"] path = lib/pendle-core-v2-public url = https://github.com/pendle-finance/pendle-core-v2-public +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 00000000..dbb6104c --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 From 0f40e1fab8436aa6c452e8960b8b29a4af6db80e Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Thu, 18 Apr 2024 13:00:31 -0400 Subject: [PATCH 03/14] forge install: openzeppelin-contracts-upgradeable v5.0.2 --- .gitmodules | 3 +++ lib/openzeppelin-contracts-upgradeable | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts-upgradeable diff --git a/.gitmodules b/.gitmodules index cce1f0e6..e45a2972 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,3 +12,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 00000000..723f8cab --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 From 673ba5406233f04a31fde907682f7cefb884fa92 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Thu, 18 Apr 2024 13:01:47 -0400 Subject: [PATCH 04/14] add pendle deps --- .gitmodules | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index e45a2972..43ccd950 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,13 +5,17 @@ [submodule "lib/forge-std"] path = lib/forge-std - url = https://github.com/foundry-rs/forge-std + url = https://github.com/foundry-rs/forge-std + [submodule "lib/pendle-core-v2-public"] path = lib/pendle-core-v2-public - url = https://github.com/pendle-finance/pendle-core-v2-public + url = https://github.com/pendle-finance/pendle-core-v2-public + [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts + branch = v4.9.3 + [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable From 82747dca73519115d9060401c7ed089db096e97d Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Thu, 18 Apr 2024 16:46:14 -0400 Subject: [PATCH 05/14] wip --- foundry.toml | 1 + src/CoveredCall/CoveredCall.sol | 4 +- src/SYCoveredCall/README.md | 172 ++ src/SYCoveredCall/SYCoveredCall.sol | 320 +++ src/SYCoveredCall/SYCoveredCallMath.sol | 491 +++++ src/SYCoveredCall/SYCoveredCallSolver.sol | 356 ++++ src/SYCoveredCall/SYCoveredCallUtils.sol | 71 + src/SYCoveredCall/covered_call.nb | 2334 +++++++++++++++++++++ 8 files changed, 3746 insertions(+), 3 deletions(-) create mode 100644 src/SYCoveredCall/README.md create mode 100644 src/SYCoveredCall/SYCoveredCall.sol create mode 100644 src/SYCoveredCall/SYCoveredCallMath.sol create mode 100644 src/SYCoveredCall/SYCoveredCallSolver.sol create mode 100644 src/SYCoveredCall/SYCoveredCallUtils.sol create mode 100644 src/SYCoveredCall/covered_call.nb diff --git a/foundry.toml b/foundry.toml index 7fc5ad0f..ca26581c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,6 +2,7 @@ remappings = [ "solmate/=lib/solstat/lib/solmate/src/", "solstat/=lib/solstat/src/", + "pendle/=lib/pendle-core-v2-public/contracts/" ] solc_version = "0.8.22" diff --git a/src/CoveredCall/CoveredCall.sol b/src/CoveredCall/CoveredCall.sol index 666591b0..198708dc 100644 --- a/src/CoveredCall/CoveredCall.sol +++ b/src/CoveredCall/CoveredCall.sol @@ -17,7 +17,7 @@ import { decodeControllerUpdate } from "src/CoveredCall/CoveredCallUtils.sol"; import { EPSILON } from "src/lib/StrategyLib.sol"; -import "forge-std/console2.sol"; +import { IStandardizedYield } from "pendle/interfaces/IStandardizedYield.sol"; enum UpdateCode { Invalid, @@ -191,8 +191,6 @@ contract CoveredCall is PairStrategy { int256 computedInvariant = tradingFunction(pool.reserves, computedL, params); - console2.log("Computed Invariant: {}", computedInvariant); - if (computedInvariant < 0 || computedInvariant > EPSILON) { revert InvalidComputedLiquidity(computedInvariant); } diff --git a/src/SYCoveredCall/README.md b/src/SYCoveredCall/README.md new file mode 100644 index 00000000..af6daa61 --- /dev/null +++ b/src/SYCoveredCall/README.md @@ -0,0 +1,172 @@ +# Log Normal Market Maker +This will be all the background needed to understand the `LogNormal` DFMM. + +## Conceptual Overview +The `LogNormal` DFMM provides the LP with a a log-normal shaped liquidity distribution centered around a price $\mu$ with a width given by $\sigma$. + +Note that this strategy can be made time-dependent by an additional $\tau$ parameter that is the time til the pool will "expire". +In this case, the LN trading function provides the LP with a payoff that is equivalent to a Black-Scholes covered call option with strike $K = \mu$, implied volatility $\sigma$, and time to expiration $\tau$. +We do not cover this explicitly here. + +## Core +We mark reserves as: +- $x \equiv \mathtt{rX}$ +- $y \equiv \mathtt{rY}$ + +`LogNormal` has two variable parameters: +- $\mu \equiv \mathtt{mean}$ +- $\sigma \equiv \mathtt{width}$ +- These parameters must satisfy: +$$\mu > 0\\ +\sigma > 0$$ + +The trading function for this DFMM is given by +$$\begin{equation} +\boxed{\varphi(x,y,L;\mu,\sigma) = \Phi^{-1}\left(\frac{x}{L}\right)+\Phi^{-1}\left(\frac{y}{\mu L}\right)+\sigma} +\end{equation}$$ +where $L$ is the **liquidity** of the pool. + +Given the domain of $\Phi^{-1}$ ([inverse Gaussian CDF](https://en.wikipedia.org/wiki/Normal_distribution)) we can see that $x\in [0,L]$ and $y\in [0,\mu L]$. +As the pool's liquidity increases, the maximal amount of each reserve increases and both are scaled by the same factor, which is also how we decide how to compute fees. + +## Useful Notation +We will use the following notation: +$$\begin{equation} +d_1(S;\mu,\sigma) = \frac{\ln\frac{S}{\mu}+\frac{1}{2}\sigma^2 }{\sigma} +\end{equation} +$$ +$$ +\begin{equation} +d_2(S;\mu,\sigma) = \frac{\ln\frac{S}{\mu}-\frac{1}{2}\sigma^2 }{\sigma} +\end{equation} +$$ + +## Price +We can provide the price of the pool given either of the reserves: +$$\begin{equation} +\boxed{P_X(x, L; \mu, \sigma) = \mu \exp\left(\Phi^{-1} \left(1 - \frac{x}{L}\right) \sigma - \frac{1}{2} \sigma^2 \right)} +\end{equation}$$ + +$$\begin{equation} +\boxed{P_Y(y, L; \mu, \sigma) = \mu \exp\left(\Phi^{-1} \left(\frac{y}{\mu L}\right) \sigma + \frac{1}{2} \sigma^2 \right)} +\end{equation}$$ + +Note that other DFMMs such as the `GeometricMean` have a price that can be determined from both reserves at once, so we typically do not write $P_X$ and $P_Y$. + +## Pool initialization +When the pool is initialized, we need to determine the value of $L$ and the other reserve. +The user will provide a price $S_0$ and an amount $x_0$ or an amount of $y_0$ that they wish to tender and we can get the other reserve and $L$ from the trading function. + +We can recall that get that: +$$\begin{equation} +\frac{x}{L} = 1-\Phi((d_1(S;\mu,\sigma)) +\end{equation}$$ +and +$$\begin{equation} +\frac{y}{\mu L} = \Phi(d_2(S;\mu,\sigma)) +\end{equation}$$ + +### Given $x$ and price +Suppose that the user specifies the amount $x_0$ they wish to allocate and they also choose a price $S_0$. +We first get $L_0$ using (6): +$$\begin{equation} +\boxed{L_0 = \frac{x}{1-\Phi(d_1(S;\mu,\sigma))}} +\end{equation}$$ +From this, we can get the amount $y_0$ +$$ +\boxed{y_0 = \mu L_0 \Phi(d_2(S;\mu,\sigma, \tau))} +$$ + + +### Given $y$ and price +The work here is basically a mirrored image of the above. +We get $L_0$: +$$\begin{equation} +\boxed{L_0 = \frac{y}{\mu\Phi(d_2(S;\mu,\sigma))}} +\end{equation}$$ +Suppose that the user specifies the amount $y$ they wish to allocate and they also choose a price $S$. +Now we need to get $x$: +$$\boxed{x_0 = L_0 \left(1-\Phi\left(d_1(S;\mu,\sigma)\right)\right)}$$ + +## Allocations and Deallocations +Allocations and deallocations should not change the price of a pool, and hence the ratio of reserves cannot change while increasing liquidity the correct amount. + +**Input $\Delta_X$:** If a user wants to allocate a specific amount of $\Delta_X$, then it must be that: +$$ +\frac{x}{L} = \frac{x+\Delta_X}{L+\Delta_L} +$$ +which yields: +$$ +\boxed{\Delta_L = L \frac{\Delta_X}{x}} +$$ +Then it must be that +$$ +\boxed{\Delta_Y = y\frac{\Delta_X}{x}} +$$ + +**Input $\Delta_Y$:** To allocate a specific amount of $\Delta_Y$, then it must be that: +$$ +\frac{y}{\mu L} = \frac{y+\Delta_Y}{\mu(L+\Delta_L)} +$$ +which yields: +$$ +\boxed{\Delta_L = L \frac{\Delta_Y}{y}} +$$ +and we likewise get +$$ +\boxed{\Delta_X = x\frac{\Delta_Y}{y}} +$$ + +## Swaps +We require that the trading function remain invariant when a swap is applied, that is: +$$\Phi^{-1}\left(\frac{x+\Delta_X}{L + \Delta_L}\right)+\Phi^{-1}\left(\frac{y}{\mu (L + \Delta_L)}\right)+\sigma = 0$$ +where either $\Delta_X$ or $\Delta_Y$ is given by user input and the $\Delta_L$ comes from fees. + +### Trade in $\Delta_X$ for $\Delta_Y$ +If we want to trade in $\Delta_X$ for $\Delta_Y$, +we first accumulate fees by taking +$$ +\textrm{Fees} = (1-\gamma) \Delta_X. +$$ +Then, we treat these fees as an allocation, therefore: +$$ +\boxed{\Delta_L = \frac{P}{Px +y}L\frac{(1-\gamma)\Delta_X}{x}} +$$ +where $P$ is the price of token $X$ quoted by the pool itself (i.e., using $P_X$ or $P_Y$ in Eq. (4) or (5) above). +Then we can use our invariant equation and solve for $\Delta_Y$ in terms of $\Delta_X$ to get: +$$\boxed{\Delta_Y = \mu (L+\Delta_L)\cdot\Phi\left(-\sigma-\Phi^{-1}\left(\frac{x+\Delta_X}{L+\Delta_L}\right)\right)-y}$$ + +### Trade in $\Delta_Y$ for $\Delta_X$ +If we want to trade in $\Delta_X$ for $\Delta_Y$, +we first accumulate fees by taking +$$ +\boxed{\Delta_L = L\frac{(1-\gamma)\Delta_X}{Px +y}} +$$ +Then we can use our invariant equation and solve for $\Delta_X$ in terms of $\Delta_Y$ to get: +$$ +\boxed{\Delta_X = (L+\Delta_L)\cdot\Phi\left(-\sigma-\Phi^{-1}\left(\frac{y+\Delta_Y}{\mu(L+\Delta_L)}\right)\right)-x} +$$ + +## Value Function on $L(S)$ +Relate to value on $V(L,S)$ and $V(x,y)$. +Then we can use this to tokenize. We have $L_X(x, S)$ and $L_Y(y, S)$. +We know that: +$$V = Sx + y$$ +We can get the following from the trading function: +$$ +x = LS\cdot\left(1-\Phi\left(\frac{\ln\frac{S}{\mu}+\frac{1}{2}\sigma^2}{\sigma}\right)\right)\\ +y = \mu\cdot L\cdot \Phi\left(\frac{\ln\frac{S}{\mu}-\frac{1}{2}\sigma^2}{\sigma}\right) +$$ +Therefore: +$$ +\boxed{V(L,S) = L\left( S\cdot\left(1-\Phi\left(\frac{\ln\frac{S}{\mu}+\frac{1}{2}\sigma^2}{\sigma}\right)\right) + \mu\cdot \Phi\left(\frac{\ln\frac{S}{\mu}-\frac{1}{2}\sigma^2}{\sigma}\right)\right)} +$$ + +### Time Dependence +Note that $L$ effectively changes as parameters of the trading function change. +To see this, note that the trading function must always satisfy: +$$\Phi^{-1}\left(\frac{x}{L}\right)+\Phi^{-1}\left(\frac{y}{ +\mu L}\right) + \sigma = 0.$$ +For new parameters $\mu'$ and $\sigma'$ we must find an $L'$ so that the trading function is satisfied: +$$\Phi^{-1}\left(\frac{x}{L'}\right)+\Phi^{-1}\left(\frac{y}{\mu'L'}\right) + \sigma' = 0.$$ +We can find this new $L'$ using a root finding algorithm. \ No newline at end of file diff --git a/src/SYCoveredCall/SYCoveredCall.sol b/src/SYCoveredCall/SYCoveredCall.sol new file mode 100644 index 00000000..e44370d9 --- /dev/null +++ b/src/SYCoveredCall/SYCoveredCall.sol @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.22; + +import { Pool } from "src/interfaces/IDFMM.sol"; +import { PairStrategy, IStrategy } from "src/PairStrategy.sol"; +import { IDFMM } from "src/interfaces/IDFMM.sol"; +import { DynamicParamLib, DynamicParam } from "src/lib/DynamicParamLib.sol"; +import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; +import { + computeTradingFunction, + computeDeltaGivenDeltaLRoundUp, + computeDeltaGivenDeltaLRoundDown, + computeDeltaLXIn, + computeDeltaLYIn, + computeTau +} from "src/SYCoveredCall/SYCoveredCallMath.sol"; +import { + decodeFeeUpdate, + decodeControllerUpdate +} from "src/SYCoveredCall/SYCoveredCallUtils.sol"; +import { EPSILON } from "src/lib/StrategyLib.sol"; +import { IPPrincipalToken } from "pendle/interfaces/IPPrincipalToken.sol"; +import { IStandardizedYield } from "pendle/interfaces/IStandardizedYield.sol"; +import { IPYieldToken } from "pendle/interfaces/IPYieldToken.sol"; + +enum UpdateCode { + Invalid, + SwapFee, + Controller +} + +struct InternalParams { + uint256 meanAnchor; + uint256 mean; + uint256 width; + uint256 maturity; + + uint256 swapFee; + address controller; + + IStandardizedYield SY; + IPPrincipalToken PT; + IPYieldToken YT; +} + +/// @dev Parameterization of the Log Normal curve. +struct SYCoveredCallParams { + uint256 meanAnchor; + uint256 mean; + uint256 width; + uint256 maturity; + + uint256 swapFee; + address controller; + + uint256 timestamp; + + IStandardizedYield SY; + IPPrincipalToken PT; + IPYieldToken YT; +} + +/// @dev Thrown when the mean parameter is not within the allowed bounds. +error InvalidMean(); + +/// @dev Thrown when the width parameter is not within the allowed bounds. +error InvalidWidth(); + +/// @dev Thrown when the maturity parameter is not later than the current block.timestamp. +error InvalidMaturity(); + +/// @dev Thrown when the pool SY token is not associated with the pool PT token. +error InvalidPair(); + +/// @dev Thrown when meanAnchor <= ONE. +error InvalidMeanAnchor(); + +/// @dev Thrown when the computedL passed to swap does not satisfy the invariant check +error InvalidComputedLiquidity(int256 invariant); + +uint256 constant MIN_WIDTH = 1; +uint256 constant MAX_WIDTH = uint256(type(int256).max); +uint256 constant MIN_MEAN = 1; +uint256 constant MAX_MEAN = uint256(type(int256).max); + +/** + * @title SYCoveredCall Strategy for DFMM. + * @author Primitive + */ +contract SYCoveredCall is PairStrategy { + using FixedPointMathLib for int256; + /// @inheritdoc IStrategy + string public constant override name = "SYCoveredCall"; + + mapping(uint256 => InternalParams) public internalParams; + + /// @param dfmm_ Address of the DFMM contract. + constructor(address dfmm_) PairStrategy(dfmm_) { } + + /// @inheritdoc IStrategy + function init( + address, + uint256 poolId, + Pool calldata pool, + bytes calldata data + ) + public + onlyDFMM + returns ( + bool valid, + int256 invariant, + uint256[] memory reserves, + uint256 totalLiquidity + ) + { + SYCoveredCallParams memory params; + + (reserves, totalLiquidity, params) = + abi.decode(data, (uint256[], uint256, SYCoveredCallParams)); + + IStandardizedYield SY = IStandardizedYield(pool.tokens[1]); + IPPrincipalToken PT = IPPrincipalToken(pool.tokens[1]); + params.timestamp = block.timestamp; + + int256 tau = int256(computeTau(params)); + + if (PT.SY() != address(SY)) { + revert InvalidPair(); + } + + if (PT.expiry() <= block.timestamp) { + revert InvalidMaturity(); + } + + if (params.meanAnchor <= 1 ether) { + revert InvalidMeanAnchor(); + } + + if (pool.reserves.length != 2 || reserves.length != 2) { + revert InvalidReservesLength(); + } + + internalParams[poolId].SY = SY; + internalParams[poolId].PT = PT; + internalParams[poolId].YT = IPYieldToken(PT.YT()); + + internalParams[poolId].maturity = internalParams[poolId].PT.expiry(); + internalParams[poolId].meanAnchor = params.meanAnchor; + internalParams[poolId].mean = uint256(int256(params.meanAnchor).powWad(tau)); + internalParams[poolId].width = params.width; + internalParams[poolId].swapFee = params.swapFee; + internalParams[poolId].controller = params.controller; + + invariant = + tradingFunction(reserves, totalLiquidity, abi.encode(params)); + valid = invariant >= 0 && invariant <= EPSILON; + } + + /// @inheritdoc IStrategy + function update( + address sender, + uint256 poolId, + Pool calldata, + bytes calldata data + ) external onlyDFMM { + if (sender != internalParams[poolId].controller) revert InvalidSender(); + UpdateCode updateCode = abi.decode(data, (UpdateCode)); + if (updateCode == UpdateCode.SwapFee) { + internalParams[poolId].swapFee = decodeFeeUpdate(data); + } else if (updateCode == UpdateCode.Controller) { + internalParams[poolId].controller = decodeControllerUpdate(data); + } else { + revert InvalidUpdateCode(); + } + } + + /// @inheritdoc IStrategy + function getPoolParams(uint256 poolId) + public + view + override + returns (bytes memory) + { + SYCoveredCallParams memory params; + + params.width = internalParams[poolId].width; + params.mean = internalParams[poolId].mean; + params.swapFee = internalParams[poolId].swapFee; + params.maturity = internalParams[poolId].maturity; + params.timestamp = IDFMM(dfmm).pools(poolId).lastSwapTimestamp; + + return abi.encode(params); + } + + /// @inheritdoc IStrategy + function validateSwap( + address, + uint256 poolId, + Pool memory pool, + bytes memory data + ) + external + view + override + returns ( + bool valid, + int256 invariant, + uint256 tokenInIndex, + uint256 tokenOutIndex, + uint256 amountIn, + uint256 amountOut, + uint256 deltaLiquidity + ) + { + bytes memory params = getPoolParams(poolId); + uint256 computedL; + (tokenInIndex, tokenOutIndex, amountIn, amountOut, computedL) = + abi.decode(data, (uint256, uint256, uint256, uint256, uint256)); + + int256 computedInvariant = + tradingFunction(pool.reserves, computedL, params); + + if (computedInvariant < 0 || computedInvariant > EPSILON) { + revert InvalidComputedLiquidity(computedInvariant); + } + + deltaLiquidity = _computeSwapDeltaLiquidity( + pool, params, tokenInIndex, tokenOutIndex, amountIn, amountOut + ); + + pool.reserves[tokenInIndex] += amountIn; + pool.reserves[tokenOutIndex] -= amountOut; + + invariant = + tradingFunction(pool.reserves, computedL + deltaLiquidity, params); + + valid = invariant >= 0; + //valid = invariant >= 0 && invariant <= EPSILON; + } + + /// @inheritdoc IStrategy + function tradingFunction( + uint256[] memory reserves, + uint256 totalLiquidity, + bytes memory params + ) public pure override returns (int256) { + SYCoveredCallParams memory poolParams = + abi.decode(params, (SYCoveredCallParams)); + return computeTradingFunction( + reserves[0], reserves[1], totalLiquidity, poolParams + ); + } + + /// @inheritdoc PairStrategy + function _computeAllocateDeltasGivenDeltaL( + uint256 deltaLiquidity, + Pool memory pool, + bytes memory + ) internal pure override returns (uint256[] memory) { + uint256[] memory deltas = new uint256[](2); + + deltas[0] = computeDeltaGivenDeltaLRoundUp( + pool.reserves[0], deltaLiquidity, pool.totalLiquidity + ); + + deltas[1] = computeDeltaGivenDeltaLRoundUp( + pool.reserves[1], deltaLiquidity, pool.totalLiquidity + ); + + return deltas; + } + + /// @inheritdoc PairStrategy + function _computeDeallocateDeltasGivenDeltaL( + uint256 deltaLiquidity, + Pool memory pool, + bytes memory + ) internal pure override returns (uint256[] memory) { + uint256[] memory deltas = new uint256[](2); + + deltas[0] = computeDeltaGivenDeltaLRoundDown( + pool.reserves[0], deltaLiquidity, pool.totalLiquidity + ); + + deltas[1] = computeDeltaGivenDeltaLRoundDown( + pool.reserves[1], deltaLiquidity, pool.totalLiquidity + ); + return deltas; + } + + function _computeSwapDeltaLiquidity( + Pool memory pool, + bytes memory params, + uint256 tokenInIndex, + uint256, + uint256 amountIn, + uint256 + ) internal pure override returns (uint256) { + SYCoveredCallParams memory poolParams = + abi.decode(params, (SYCoveredCallParams)); + + if (tokenInIndex == 0) { + return computeDeltaLXIn( + amountIn, + pool.reserves[0], + pool.reserves[1], + pool.totalLiquidity, + poolParams + ); + } + + return computeDeltaLYIn( + amountIn, + pool.reserves[0], + pool.reserves[1], + pool.totalLiquidity, + poolParams + ); + } +} diff --git a/src/SYCoveredCall/SYCoveredCallMath.sol b/src/SYCoveredCall/SYCoveredCallMath.sol new file mode 100644 index 00000000..9894d5e5 --- /dev/null +++ b/src/SYCoveredCall/SYCoveredCallMath.sol @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.13; + +import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; +import { SignedWadMathLib } from "src/lib/SignedWadMath.sol"; +import { ONE, HALF } from "src/lib/StrategyLib.sol"; +import { SYCoveredCallParams } from "src/SYCoveredCall/SYCoveredCall.sol"; +import { Gaussian } from "solstat/Gaussian.sol"; +import { toUint } from "src/SYCoveredCall/SYCoveredCallUtils.sol"; +import { bisection } from "src/lib/BisectionLib.sol"; +import "forge-std/console2.sol"; + +using FixedPointMathLib for uint256; +using FixedPointMathLib for int256; +using SignedWadMathLib for int256; + +uint256 constant MAX_ITER = 256; +uint256 constant YEAR = 31_536_000; + +function computeTradingFunction( + uint256 rX, + uint256 rY, + uint256 L, + SYCoveredCallParams memory params +) pure returns (int256) { + int256 a = Gaussian.ppf(int256(rX.divWadUp(L))); + int256 b = Gaussian.ppf(int256(rY.divWadUp(L.mulWadUp(params.mean)))); + uint256 tau = computeTau(params); + int256 c = int256(computeSigmaSqrtTau(params.width, tau)); + return a + b + c; +} + +function computeTau(SYCoveredCallParams memory params) pure returns (uint256) { + if (params.timestamp >= params.maturity) { + return 0; + } else { + return ONE * (params.maturity - params.timestamp) / YEAR; + } +} +function computeDeltaGivenDeltaLRoundUp( + uint256 reserve, + uint256 deltaLiquidity, + uint256 totalLiquidity +) pure returns (uint256) { + return reserve.mulDivUp(deltaLiquidity, totalLiquidity); +} + +function computeDeltaGivenDeltaLRoundDown( + uint256 reserve, + uint256 deltaLiquidity, + uint256 totalLiquidity +) pure returns (uint256) { + return reserve.mulDivDown(deltaLiquidity, totalLiquidity); +} + +function computeLnSDivMean( + uint256 S, + uint256 mean +) pure returns (int256 lnSDivMean) { + lnSDivMean = int256(S.divWadUp(mean)).lnWad(); +} + +/** + * @dev Computes the half of the square of sigma. + * + * $$\frac{1}{2}\sigma^2$$ + * + */ +function computeHalfSigmaSquaredTau( + uint256 sigma, + uint256 tau +) pure returns (uint256) { + uint256 innerTerm = sigma.mulWadDown(sigma).mulWadDown(tau); + return HALF.mulWadDown(innerTerm); +} + +function computeSigmaSqrtTau( + uint256 sigma, + uint256 tau +) pure returns (uint256 sigmaSqrtTau) { + uint256 sqrtTau = FixedPointMathLib.sqrt(tau) * 10 ** 9; + sigmaSqrtTau = sigma.mulWadUp(sqrtTau); +} + +/** + * @dev Computes reserves L given rx, S. + * + * $$L_0 = \frac{x}{1-\Phi(d_1(S;\mu,\sigma))}$$ + * + * @param rx The reserve of x. + * @param S The price of X in Y, in WAD units. + * @param params LogNormParameters of the Log Normal distribution. + * @return L The liquidity given rx, S + */ +function computeLGivenX( + uint256 rx, + uint256 S, + SYCoveredCallParams memory params +) pure returns (uint256 L) { + int256 d1 = computeD1({ S: S, params: params }); + uint256 cdf = toUint(Gaussian.cdf(d1)); + + L = rx.divWadUp(ONE - cdf); +} + +function computeLGivenY( + uint256 ry, + uint256 S, + SYCoveredCallParams memory params +) pure returns (uint256 L) { + int256 d2 = computeD2({ S: S, params: params }); + uint256 cdf = toUint(Gaussian.cdf(d2)); + + L = ry.divWadUp(params.mean.mulWadUp(cdf)); +} + +/// @dev Computes reserves y given L(x, S). +/// @return ry The reserve y computed as y(x, s) = K * L_x(x, S) * cdf[d2(S, K, sigma, tau)] +function computeYGivenL( + uint256 L, + uint256 S, + SYCoveredCallParams memory params +) pure returns (uint256 ry) { + int256 d2 = computeD2({ S: S, params: params }); + uint256 cdf = toUint(Gaussian.cdf(d2)); + + ry = params.mean.mulWadUp(L).mulWadUp(cdf); +} + +/// @dev Computes reserves x given L(y, S). +/// @return rx The reserve x computed as x(y, s) = L_y(y, S) * (WAD - cdf[d1(S, K, sigma, tau)]) +function computeXGivenL( + uint256 L, + uint256 S, + SYCoveredCallParams memory params +) pure returns (uint256 rx) { + int256 d1 = computeD1({ S: S, params: params }); + uint256 cdf = toUint(Gaussian.cdf(d1)); + rx = L.mulWadUp(ONE - cdf); +} + +/** + * @dev Computes the d1 parameter for the Black-Scholes formula. + * + * $$d_1(S;\mu,\sigma) = \frac{\ln\frac{S}{\mu}+\frac{1}{2}\sigma^2 }{\sigma}$$ + * + * @param S The price of X in Y, in WAD units. + * @param params LogNormParameters of the Log Normal distribution. + */ +function computeD1( + uint256 S, + SYCoveredCallParams memory params +) pure returns (int256 d1) { + uint256 tau = computeTau(params); + if (tau == 0) { + d1 = 0; + } else { + int256 lnSDivMean = computeLnSDivMean(S, params.mean); + uint256 halfSigmaSquaredTau = + computeHalfSigmaSquaredTau(params.width, tau); + uint256 sigmaSqrtTau = computeSigmaSqrtTau(params.width, tau); + d1 = (lnSDivMean + int256(halfSigmaSquaredTau)).wadDiv( + int256(sigmaSqrtTau) + ); + } +} + +/// @dev Computes the d2 parameter for the Black-Scholes formula. +/// $$d_2(S;\mu,\sigma) = \frac{\ln\frac{S}{K}-\frac{1}{2}\sigma^2 }{\sigma}$$ +/// @param S The price of X in Y, in WAD units. +/// @param params LogNormParameters of the Log Normal distribution. +/// @return d2 = d1 - sigma * sqrt(tau), alternatively d2 = (ln(S/K) - tau * sigma^2 / 2) / (sigma * sqrt(tau)) +function computeD2( + uint256 S, + SYCoveredCallParams memory params +) pure returns (int256 d2) { + uint256 tau = computeTau(params); + if (tau == 0) { + d2 = 0; + } else { + int256 lnSDivMean = computeLnSDivMean(S, params.mean); + uint256 halfSigmaSquaredTau = + computeHalfSigmaSquaredTau(params.width, tau); + uint256 sigmaSqrtTau = computeSigmaSqrtTau(params.width, tau); + d2 = (lnSDivMean - int256(halfSigmaSquaredTau)).wadDiv( + int256(sigmaSqrtTau) + ); + } +} + +/** + * @dev Computes the price using the reserve of token X. + * + * $$P_X(x, L; \mu, \sigma) = \mu \exp (\Phi^{-1} (1 - \frac{x}{L} ) \sigma - \frac{1}{2} \sigma^2 )$$ + * + */ +function computePriceGivenX( + uint256 rX, + uint256 L, + SYCoveredCallParams memory params +) pure returns (uint256) { + uint256 tau = computeTau(params); + uint256 a = computeHalfSigmaSquaredTau(params.width, tau); + // $$\Phi^{-1} (1 - \frac{x}{L})$$ + int256 b = Gaussian.ppf(int256(ONE - rX.divWadDown(L))); + + // $$\exp(\Phi^{-1} (1 - \frac{x}{L} ) \sigma - \frac{1}{2} \sigma^2 )$$ + int256 exp = ( + b.wadMul(int256(computeSigmaSqrtTau(params.width, tau))) - int256(a) + ).expWad(); + + // $$\mu \exp (\Phi^{-1} (1 - \frac{x}{L} ) \sigma - \frac{1}{2} \sigma^2 )$$ + return params.mean.mulWadUp(uint256(exp)); +} + +// K = P1(x) / exp[ni(x/L)√(L + (1/2)v²t)] +function computeKGivenLastPrice(uint256 rX, uint256 L, SYCoveredCallParams memory params) pure returns (uint256 K) { + uint256 price = computePriceGivenX(rX, L, params); + + uint256 tau = computeTau(params); + uint256 a = computeHalfSigmaSquaredTau(params.width, tau); + // $$\Phi^{-1} (1 - \frac{x}{L})$$ + int256 b = Gaussian.ppf(int256(ONE - rX.divWadDown(L))); + int256 exp = ( + b.wadMul(int256(computeSigmaSqrtTau(params.width, tau))) - int256(a) + ).expWad(); + + K = price.divWadDown(exp); + + +} + + +function computePriceGivenY( + uint256 rY, + uint256 L, + SYCoveredCallParams memory params +) pure returns (uint256) { + uint256 tau = computeTau(params); + uint256 a = computeHalfSigmaSquaredTau(params.width, tau); + + // $$\Phi^{-1} (\frac{y}{\mu L})$$ + int256 b = Gaussian.ppf(int256(rY.divWadDown(params.mean.mulWadDown(L)))); + + // $$\exp (\Phi^{-1} (\frac{y}{\mu L}) \sigma + \frac{1}{2} \sigma^2 )$$ + int256 exp = ( + b.wadMul(int256(computeSigmaSqrtTau(params.width, tau))) + int256(a) + ).expWad(); + + // $$\mu \exp (\Phi^{-1} (\frac{y}{\mu L}) \sigma + \frac{1}{2} \sigma^2 )$$ + return params.mean.mulWadUp(uint256(exp)); +} + +function computeDeltaLXIn( + uint256 amountIn, + uint256 rx, + uint256 ry, + uint256 L, + SYCoveredCallParams memory params +) pure returns (uint256 deltaL) { + uint256 fees = params.swapFee.mulWadUp(amountIn); + uint256 px = computePriceGivenX(rx, L, params); + deltaL = px.mulWadUp(L).mulWadUp(fees).divWadDown(px.mulWadDown(rx) + ry); +} + +function computeDeltaLYIn( + uint256 amountIn, + uint256 rx, + uint256 ry, + uint256 L, + SYCoveredCallParams memory params +) pure returns (uint256 deltaL) { + uint256 fees = params.swapFee.mulWadUp(amountIn); + uint256 px = computePriceGivenX(rx, L, params); + deltaL = L.mulWadUp(fees).divWadDown(px.mulWadDown(rx) + ry); +} + +function computeAllocationGivenDeltaX( + uint256 deltaX, + uint256 rX, + uint256 rY, + uint256 totalLiquidity +) pure returns (uint256 deltaY, uint256 deltaL) { + uint256 a = deltaX.divWadUp(rX); + deltaY = a.mulWadUp(rY); + deltaL = a.mulWadUp(totalLiquidity); +} + +function computeAllocationGivenDeltaY( + uint256 deltaY, + uint256 rX, + uint256 rY, + uint256 totalLiquidity +) pure returns (uint256 deltaX, uint256 deltaL) { + uint256 a = deltaY.divWadUp(rY); + deltaX = a.mulWadUp(rX); + deltaL = a.mulWadUp(totalLiquidity); +} + +function computeDeallocationGivenDeltaX( + uint256 deltaX, + uint256 rX, + uint256 rY, + uint256 totalLiquidity +) pure returns (uint256 deltaY, uint256 deltaL) { + uint256 a = deltaX.divWadDown(rX); + deltaY = a.mulWadDown(rY); + deltaL = a.mulWadDown(totalLiquidity); +} + +function computeDeallocationGivenDeltaY( + uint256 deltaY, + uint256 rX, + uint256 rY, + uint256 totalLiquidity +) pure returns (uint256 deltaX, uint256 deltaL) { + uint256 a = deltaY.divWadDown(rY); + deltaX = a.mulWadDown(rX); + deltaL = a.mulWadDown(totalLiquidity); +} + +/// @dev This is a pure anonymous function defined at the file level, which allows +/// it to be passed as an argument to another function. BisectionLib.sol takes this +/// function as an argument to find the root of the trading function given the reserveYWad. +function findRootY(bytes memory data, uint256 ry) pure returns (int256) { + (uint256 rx, uint256 L, SYCoveredCallParams memory params) = + abi.decode(data, (uint256, uint256, SYCoveredCallParams)); + return computeTradingFunction(rx, ry, L, params); +} + +/// @dev This is a pure anonymous function defined at the file level, which allows +/// it to be passed as an argument to another function. BisectionLib.sol takes this +/// function as an argument to find the root of the trading function given the reserveXWad. +function findRootX(bytes memory data, uint256 rx) pure returns (int256) { + (uint256 ry, uint256 L, SYCoveredCallParams memory params) = + abi.decode(data, (uint256, uint256, SYCoveredCallParams)); + return computeTradingFunction(rx, ry, L, params); +} + +/// @dev This is a pure anonymous function defined at the file level, which allows +/// it to be passed as an argument to another function. BisectionLib.sol takes this +/// function as an argument to find the root of the trading function given the liquidity. +function findRootLiquidity( + bytes memory data, + uint256 L +) pure returns (int256) { + (uint256 rx, uint256 ry, SYCoveredCallParams memory params) = + abi.decode(data, (uint256, uint256, SYCoveredCallParams)); + return computeTradingFunction(rx, ry, L, params); +} + +function computeNextLiquidity( + uint256 rX, + uint256 rY, + int256 invariant, + uint256 approximatedL, + SYCoveredCallParams memory params +) pure returns (uint256 L) { + uint256 upper = approximatedL; + uint256 lower = approximatedL; + int256 computedInvariant = invariant; + if (computedInvariant < 0) { + while (computedInvariant < 0) { + lower = lower.mulDivDown(999, 1000); + uint256 min = rX > rY.divWadDown(params.mean) + ? rX + 1000 + : rY.divWadDown(params.mean) + 1000; + lower = lower < rX ? min : lower; + computedInvariant = computeTradingFunction({ + rX: rX, + rY: rY, + L: lower, + params: params + }); + } + } else { + while (computedInvariant > 0) { + upper = upper.mulDivUp(1001, 1000); + computedInvariant = computeTradingFunction({ + rX: rX, + rY: rY, + L: upper, + params: params + }); + } + } + (uint256 rootInput,, uint256 lowerInput) = bisection( + abi.encode(rX, rY, params), lower, upper, 1, MAX_ITER, findRootLiquidity + ); + + if ( + computeTradingFunction({ rX: rX, rY: rY, L: rootInput, params: params }) + == 0 + ) { + L = rootInput; + } else { + L = lowerInput; + } +} + +function computeNextRx( + uint256 rY, + uint256 L, + int256 invariant, + uint256 approximatedRx, + SYCoveredCallParams memory params +) pure returns (uint256 rX) { + uint256 upper = approximatedRx; + uint256 lower = approximatedRx; + int256 computedInvariant = invariant; + if (computedInvariant < 0) { + while (computedInvariant < 0) { + upper = upper.mulDivUp(1001, 1000); + upper = upper > L ? L : upper; + computedInvariant = computeTradingFunction({ + rX: upper, + rY: rY, + L: L, + params: params + }); + } + } else { + while (computedInvariant > 0) { + lower = lower.mulDivDown(999, 1000); + lower = lower > L ? L : lower; + computedInvariant = computeTradingFunction({ + rX: lower, + rY: rY, + L: L, + params: params + }); + } + } + (uint256 rootInput, uint256 upperInput,) = bisection( + abi.encode(rY, L, params), lower, upper, 0, MAX_ITER, findRootX + ); + // `upperInput` should be positive, so if root is < 0 return upperInput instead + if ( + computeTradingFunction({ rX: rootInput, rY: rY, L: L, params: params }) + == 0 + ) { + rX = rootInput; + } else { + rX = upperInput; + } +} + +function computeNextRy( + uint256 rX, + uint256 L, + int256 invariant, + uint256 approximatedRy, + SYCoveredCallParams memory params +) pure returns (uint256 rY) { + uint256 upper = approximatedRy; + uint256 lower = approximatedRy; + int256 computedInvariant = invariant; + if (computedInvariant < 0) { + while (computedInvariant < 0) { + upper = upper.mulDivUp(1001, 1000); + computedInvariant = computeTradingFunction({ + rX: rX, + rY: upper, + L: L, + params: params + }); + } + } else { + while (computedInvariant > 0) { + lower = lower.mulDivDown(999, 1000); + computedInvariant = computeTradingFunction({ + rX: rX, + rY: lower, + L: L, + params: params + }); + } + } + (uint256 rootInput, uint256 upperInput,) = bisection( + abi.encode(rX, L, params), lower, upper, 0, MAX_ITER, findRootY + ); + // `upperInput` should be positive, so if root is < 0 return upperInput instead + if ( + computeTradingFunction({ rX: rX, rY: rootInput, L: L, params: params }) + == 0 + ) { + rY = rootInput; + } else { + rY = upperInput; + } +} diff --git a/src/SYCoveredCall/SYCoveredCallSolver.sol b/src/SYCoveredCall/SYCoveredCallSolver.sol new file mode 100644 index 00000000..65d1ac4d --- /dev/null +++ b/src/SYCoveredCall/SYCoveredCallSolver.sol @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.22; + +import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; +import { IStrategy } from "src/interfaces/IStrategy.sol"; +import { Pool, IDFMM } from "src/interfaces/IDFMM.sol"; +import { SignedWadMathLib } from "src/lib/SignedWadMath.sol"; +import { + computeAllocationGivenX, + computeAllocationGivenY +} from "src/lib/StrategyLib.sol"; +import { + encodeFeeUpdate, + encodeControllerUpdate, + computeInitialPoolData, + computeInitialPoolDataGivenY +} from "src/SYCoveredCall/SYCoveredCallUtils.sol"; +import { SYCoveredCallParams } from "src/SYCoveredCall/SYCoveredCall.sol"; +import { + computeTradingFunction, + computeNextLiquidity, + computeXGivenL, + computeNextRx, + computeYGivenL, + computeNextRy, + computePriceGivenX, + computePriceGivenY, + computeDeltaLXIn, + computeDeltaLYIn, + computeAllocationGivenDeltaX, + computeAllocationGivenDeltaY, + computeDeallocationGivenDeltaX, + computeDeallocationGivenDeltaY, + YEAR, + ONE +} from "src/SYCoveredCall/SYCoveredCallMath.sol"; +import "forge-std/console2.sol"; + +contract SYCoveredCallSolver { + using FixedPointMathLib for uint256; + using FixedPointMathLib for int256; + using SignedWadMathLib for int256; + + /// @dev Structure to hold reserve information + struct Reserves { + uint256 rx; + uint256 ry; + uint256 L; + } + + uint256 public constant BISECTION_EPSILON = 0; + uint256 public constant MAX_BISECTION_ITERS = 120; + + address public strategy; + + constructor(address _strategy) { + strategy = _strategy; + } + + function getPoolParams(uint256 poolId) + public + view + returns (SYCoveredCallParams memory) + { + return abi.decode( + IStrategy(strategy).getPoolParams(poolId), (SYCoveredCallParams) + ); + } + + function getPoolParamsCustomTimestamp( + uint256 poolId, + uint256 timestamp + ) public view returns (SYCoveredCallParams memory) { + SYCoveredCallParams memory params = getPoolParams(poolId); + params.timestamp = timestamp; + return params; + } + + function prepareFeeUpdate(uint256 swapFee) + external + pure + returns (bytes memory) + { + return encodeFeeUpdate(swapFee); + } + + function prepareControllerUpdate(address controller) + external + pure + returns (bytes memory) + { + return encodeControllerUpdate(controller); + } + + function getReservesAndLiquidity(uint256 poolId) + public + view + returns (uint256[] memory, uint256) + { + Pool memory pool = IDFMM(IStrategy(strategy).dfmm()).pools(poolId); + return (pool.reserves, pool.totalLiquidity); + } + + function getInitialPoolDataGivenX( + uint256 rX, + uint256 S, + SYCoveredCallParams memory params + ) public pure returns (bytes memory) { + return computeInitialPoolData(rX, S, params); + } + + function getInitialPoolDataGivenY( + uint256 rY, + uint256 S, + SYCoveredCallParams memory params + ) public pure returns (bytes memory) { + return computeInitialPoolDataGivenY(rY, S, params); + } + + function prepareInitialPoolDataGivenY( + uint256 rY, + uint256 S, + SYCoveredCallParams memory params + ) public pure returns (bytes memory) { + return computeInitialPoolDataGivenY(rY, S, params); + } + + function allocateGivenDeltaX( + uint256 poolId, + uint256 deltaX + ) public view returns (uint256 deltaY, uint256 deltaLiquidity) { + (uint256[] memory reserves, uint256 liquidity) = + getReservesAndLiquidity(poolId); + (deltaY, deltaLiquidity) = computeAllocationGivenDeltaX( + deltaX, reserves[0], reserves[1], liquidity + ); + } + + function allocateGivenDeltaY( + uint256 poolId, + uint256 deltaY + ) public view returns (uint256 deltaX, uint256 deltaLiquidity) { + (uint256[] memory reserves, uint256 liquidity) = + getReservesAndLiquidity(poolId); + (deltaX, deltaLiquidity) = computeAllocationGivenDeltaY( + deltaY, reserves[0], reserves[1], liquidity + ); + } + + function deallocateGivenDeltaX( + uint256 poolId, + uint256 deltaX + ) public view returns (uint256 deltaY, uint256 deltaLiquidity) { + (uint256[] memory reserves, uint256 liquidity) = + getReservesAndLiquidity(poolId); + (deltaY, deltaLiquidity) = computeDeallocationGivenDeltaX( + deltaX, reserves[0], reserves[1], liquidity + ); + } + + function deallocateGivenDeltaY( + uint256 poolId, + uint256 deltaY + ) public view returns (uint256 deltaX, uint256 deltaLiquidity) { + (uint256[] memory reserves, uint256 liquidity) = + getReservesAndLiquidity(poolId); + (deltaX, deltaLiquidity) = computeDeallocationGivenDeltaY( + deltaY, reserves[0], reserves[1], liquidity + ); + } + + function getNextLiquidity( + uint256 poolId, + uint256 rx, + uint256 ry, + uint256 L + ) public view returns (uint256) { + SYCoveredCallParams memory poolParams = + getPoolParamsCustomTimestamp(poolId, block.timestamp); + + int256 invariant = computeTradingFunction(rx, ry, L, poolParams); + return computeNextLiquidity(rx, ry, invariant, L, poolParams); + } + + function getNextReserveX( + uint256 poolId, + uint256 ry, + uint256 L, + uint256 S + ) public view returns (uint256) { + SYCoveredCallParams memory poolParams = + getPoolParamsCustomTimestamp(poolId, block.timestamp); + uint256 approximatedRx = computeXGivenL(L, S, poolParams); + int256 invariant = + computeTradingFunction(approximatedRx, ry, L, poolParams); + return computeNextRx(ry, L, invariant, approximatedRx, poolParams); + } + + function getNextReserveY( + uint256 poolId, + uint256 rx, + uint256 L, + uint256 S + ) public view returns (uint256) { + SYCoveredCallParams memory poolParams = + getPoolParamsCustomTimestamp(poolId, block.timestamp); + uint256 approximatedRy = computeYGivenL(L, S, poolParams); + int256 invariant = + computeTradingFunction(rx, approximatedRy, L, poolParams); + return computeNextRy(rx, L, invariant, approximatedRy, poolParams); + } + + struct SimulateSwapState { + uint256 amountOut; + uint256 deltaLiquidity; + uint256 fees; + } + + /// @dev Estimates a swap's reserves and adjustments and returns its validity. + function simulateSwap( + uint256 poolId, + bool swapXIn, + uint256 amountIn + ) public view returns (bool, uint256, uint256, bytes memory) { + Reserves memory endReserves; + (uint256[] memory preReserves, uint256 preTotalLiquidity) = + getReservesAndLiquidity(poolId); + SYCoveredCallParams memory poolParams = + getPoolParamsCustomTimestamp(poolId, block.timestamp); + + SimulateSwapState memory state; + + uint256 startComputedL = getNextLiquidity( + poolId, preReserves[0], preReserves[1], preTotalLiquidity + ); + { + console2.log("startComputedL", startComputedL); + + if (swapXIn) { + state.deltaLiquidity = computeDeltaLXIn( + amountIn, + preReserves[0], + preReserves[1], + preTotalLiquidity, + poolParams + ); + console2.log("state.deltaLiquidity", state.deltaLiquidity); + + endReserves.rx = preReserves[0] + amountIn; + endReserves.L = startComputedL + state.deltaLiquidity; + console2.log("endReserves.rx", endReserves.rx); + console2.log("endReserves.L", endReserves.L); + uint256 approxPrice = + getPriceGivenXL(poolId, endReserves.rx, endReserves.L); + console2.log("approxPrice", approxPrice); + + endReserves.ry = getNextReserveY( + poolId, endReserves.rx, endReserves.L, approxPrice + ); + console2.log("endReserves.ry", endReserves.ry); + + require( + endReserves.ry < preReserves[1], + "invalid swap: y reserve increased!" + ); + state.amountOut = preReserves[1] - endReserves.ry; + } else { + state.deltaLiquidity = computeDeltaLYIn( + amountIn, + preReserves[0], + preReserves[1], + preTotalLiquidity, + poolParams + ); + + endReserves.ry = preReserves[1] + amountIn; + endReserves.L = startComputedL + state.deltaLiquidity; + uint256 approxPrice = + getPriceGivenYL(poolId, endReserves.ry, endReserves.L); + + endReserves.rx = getNextReserveX( + poolId, endReserves.ry, endReserves.L, approxPrice + ); + + require( + endReserves.rx < preReserves[0], + "invalid swap: x reserve increased!" + ); + state.amountOut = preReserves[0] - endReserves.rx; + } + } + + Pool memory pool; + pool.reserves = preReserves; + pool.totalLiquidity = preTotalLiquidity; + + bytes memory swapData; + + if (swapXIn) { + swapData = + abi.encode(0, 1, amountIn, state.amountOut, startComputedL); + } else { + swapData = + abi.encode(1, 0, amountIn, state.amountOut, startComputedL); + } + + uint256 poolId = poolId; + (bool valid,,,,,,) = IStrategy(strategy).validateSwap( + address(this), poolId, pool, swapData + ); + + return ( + valid, + state.amountOut, + computePriceGivenX(endReserves.rx, endReserves.L, poolParams), + swapData + ); + } + + function getPriceGivenYL( + uint256 poolId, + uint256 ry, + uint256 L + ) public view returns (uint256 price) { + SYCoveredCallParams memory params = + getPoolParamsCustomTimestamp(poolId, block.timestamp); + price = computePriceGivenY(ry, L, params); + } + + function getPriceGivenXL( + uint256 poolId, + uint256 rx, + uint256 L + ) public view returns (uint256 price) { + SYCoveredCallParams memory params = + getPoolParamsCustomTimestamp(poolId, block.timestamp); + price = computePriceGivenX(rx, L, params); + } + + /// @dev Computes the internal price using this strategie's slot parameters. + function internalPrice(uint256 poolId) + public + view + returns (uint256 price) + { + (uint256[] memory reserves, uint256 L) = getReservesAndLiquidity(poolId); + price = computePriceGivenX(reserves[0], L, getPoolParams(poolId)); + } + + function getInvariant(uint256 poolId) public view returns (int256) { + (uint256[] memory reserves, uint256 L) = getReservesAndLiquidity(poolId); + return computeTradingFunction( + reserves[0], reserves[1], L, getPoolParams(poolId) + ); + } +} diff --git a/src/SYCoveredCall/SYCoveredCallUtils.sol b/src/SYCoveredCall/SYCoveredCallUtils.sol new file mode 100644 index 00000000..2fee37fb --- /dev/null +++ b/src/SYCoveredCall/SYCoveredCallUtils.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.13; + +import { SYCoveredCallParams, UpdateCode } from "src/SYCoveredCall/SYCoveredCall.sol"; +import { + computeLGivenX, + computeLGivenY, + computeXGivenL, + computeYGivenL, + computeTradingFunction, + computeNextLiquidity +} from "./SYCoveredCallMath.sol"; + +function encodeFeeUpdate(uint256 swapFee) pure returns (bytes memory) { + return abi.encode(UpdateCode.SwapFee, uint256(swapFee)); +} + +function encodeControllerUpdate(address controller) + pure + returns (bytes memory data) +{ + return abi.encode(UpdateCode.Controller, controller); +} + +function decodeFeeUpdate(bytes memory data) pure returns (uint256) { + (, uint256 swapFee) = abi.decode(data, (UpdateCode, uint256)); + return swapFee; +} + +function decodeControllerUpdate(bytes memory data) + pure + returns (address controller) +{ + (, controller) = abi.decode(data, (UpdateCode, address)); +} + +function computeInitialPoolData( + uint256 amountX, + uint256 initialPrice, + SYCoveredCallParams memory params +) pure returns (bytes memory) { + uint256 L = computeLGivenX(amountX, initialPrice, params); + uint256 ry = computeYGivenL(L, initialPrice, params); + int256 invariant = computeTradingFunction(amountX, ry, L, params); + L = computeNextLiquidity(amountX, ry, invariant, L, params); + uint256[] memory reserves = new uint256[](2); + reserves[0] = amountX; + reserves[1] = ry; + return abi.encode(reserves, L, params); +} + +function computeInitialPoolDataGivenY( + uint256 amountY, + uint256 initialPrice, + SYCoveredCallParams memory params +) pure returns (bytes memory) { + uint256 L = computeLGivenY(amountY, initialPrice, params); + uint256 rX = computeXGivenL(L, initialPrice, params); + int256 invariant = computeTradingFunction(rX, amountY, L, params); + L = computeNextLiquidity(rX, amountY, invariant, L, params); + uint256[] memory reserves = new uint256[](2); + reserves[0] = rX; + reserves[1] = amountY; + return abi.encode(reserves, L, params); +} + +/// @dev Casts a positived signed integer to an unsigned integer, reverting if `x` is negative. +function toUint(int256 x) pure returns (uint256) { + require(x >= 0, "toUint: negative"); + return uint256(x); +} diff --git a/src/SYCoveredCall/covered_call.nb b/src/SYCoveredCall/covered_call.nb new file mode 100644 index 00000000..329ffa88 --- /dev/null +++ b/src/SYCoveredCall/covered_call.nb @@ -0,0 +1,2334 @@ +(*CacheID: 234*) +(* Internal cache information: +NotebookFileLineBreakTest +NotebookFileLineBreakTest +NotebookDataPosition[ 0, 0] +NotebookDataLength[ 86695, 2333] +NotebookOptionsPosition[ 78138, 2161] +NotebookOutlinePosition[ 78697, 2180] +CellTagsIndexPosition[ 78654, 2177] +WindowFrame->Normal*) + +(* Beginning of Notebook Content *) +Notebook[{ + +Cell[CellGroupData[{ +Cell["Log Normal Trading Function Calculations", "Title", + CellChangeTimes->{{3.911382811596325*^9, + 3.9113828340058823`*^9}},ExpressionUUID->"2003d08a-fff7-4f74-8623-\ +7a0823c9cafa"], + +Cell[CellGroupData[{ + +Cell["\<\ +First, we set up the basic functions we need throughout the notebook.\ +\>", "Section", + CellChangeTimes->{{3.911382862311339*^9, + 3.91138289581577*^9}},ExpressionUUID->"514be430-48c5-4dc6-92af-\ +6b1c3a5b8586"], + +Cell[CellGroupData[{ + +Cell["\<\ +Before anything, we should set some environment level variables.\ +\>", "Subsection", + CellChangeTimes->{{3.911387263997834*^9, + 3.9113872765136137`*^9}},ExpressionUUID->"f16f1652-ed41-4414-8c9b-\ +6b6d5d8061ac"], + +Cell[BoxData[ + RowBox[{ + RowBox[{ + RowBox[{"On", "[", "Assert", "]"}], ";"}], " ", + RowBox[{"(*", " ", + RowBox[{ + RowBox[{ + "Asserts", " ", "will", " ", "show", " ", "a", " ", "failure", " ", "if", + " ", "they", " ", "fail"}], ",", " ", + RowBox[{"and", " ", "nothing", " ", "if", " ", "they", " ", + RowBox[{"don", "'"}], "t"}]}], " ", "*)"}]}]], "Code", + CellChangeTimes->{{3.91138727840687*^9, 3.911387281430051*^9}, { + 3.911387543969853*^9, 3.911387555514419*^9}}, + CellLabel-> + "In[3266]:=",ExpressionUUID->"8255c47c-fa0b-4fdd-8638-752453aca613"] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["First are the CDF and inverse CDF (PPF) functions.", "Subsection", + CellChangeTimes->{{3.9113829761574574`*^9, + 3.9113829863941193`*^9}},ExpressionUUID->"b3dd161e-0b53-4183-b30c-\ +be7844f26477"], + +Cell[BoxData[{ + RowBox[{ + RowBox[{"\[CapitalPhi]", "[", "x_", "]"}], " ", ":=", " ", + RowBox[{"CDF", "[", + RowBox[{ + RowBox[{"NormalDistribution", "[", + RowBox[{"0", ",", "1"}], "]"}], ",", " ", "x"}], "]"}]}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["\[CapitalPhi]", "inv"], "[", "y_", "]"}], " ", ":=", " ", + RowBox[{"Quantile", "[", + RowBox[{ + RowBox[{"NormalDistribution", "[", + RowBox[{"0", ",", " ", "1"}], "]"}], ",", " ", "y"}], "]"}]}]}], "Code", + CellChangeTimes->{{3.911382903714142*^9, 3.911383006799996*^9}, { + 3.911385117889493*^9, 3.911385119663499*^9}, {3.91738309829743*^9, + 3.917383102823773*^9}, {3.9173831552741337`*^9, 3.917383158507695*^9}, + 3.917383532443891*^9}, + CellLabel-> + "In[3833]:=",ExpressionUUID->"25d6c1c4-f902-41e0-8746-c63f6b03d94e"] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["\<\ +Next let\[CloseCurlyQuote]s define some helper functions. These will appear \ +often in calculations.\ +\>", "Subsection", + CellChangeTimes->{{3.911383043072701*^9, 3.911383082172174*^9}, { + 3.911383316418652*^9, + 3.9113833317783127`*^9}},ExpressionUUID->"f601d02f-f91e-4780-a166-\ +a78097a54f48"], + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + SubscriptBox["d", "1"], "[", + RowBox[{"S_", ",", "\[Mu]_", ",", "\[Sigma]_"}], "]"}], " ", ":=", " ", + FractionBox[ + RowBox[{ + RowBox[{"Log", "[", + FractionBox["S", "\[Mu]"], "]"}], " ", "+", " ", + RowBox[{ + FractionBox["1", "2"], + SuperscriptBox["\[Sigma]", "2"]}]}], "\[Sigma]"]}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["d", "2"], "[", + RowBox[{"S_", ",", "\[Mu]_", ",", "\[Sigma]_"}], "]"}], " ", ":=", " ", + FractionBox[ + RowBox[{ + RowBox[{"Log", "[", + FractionBox["S", "\[Mu]"], "]"}], " ", "-", " ", + RowBox[{ + FractionBox["1", "2"], + SuperscriptBox["\[Sigma]", "2"]}]}], "\[Sigma]"]}]}], "Code", + CellChangeTimes->{{3.911383086202894*^9, 3.911383096527341*^9}, { + 3.911383144055451*^9, 3.911383310823001*^9}, {3.9113851030677443`*^9, + 3.91138511600043*^9}, 3.9185709112502613`*^9}, + CellLabel-> + "In[3835]:=",ExpressionUUID->"d5d16a82-3e60-44ff-affc-b50eb9144304"] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["\<\ +Now let\[CloseCurlyQuote]s define functions that are more explicitly used for \ +the DFMM.\ +\>", "Subsection", + CellChangeTimes->{{3.911383368894425*^9, 3.9113833696600657`*^9}, { + 3.911383542720358*^9, + 3.911383554344432*^9}},ExpressionUUID->"009a24ad-ebe5-4d73-bdda-\ +a7839592332a"], + +Cell[CellGroupData[{ + +Cell["\<\ +These are functions used to get initial liquidity given a token amount and a \ +price.\ +\>", "Subsubsection", + CellChangeTimes->{{3.911383821691424*^9, + 3.911383842953394*^9}},ExpressionUUID->"3b15f5e3-f420-4095-899a-\ +506c7286cc40"], + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + SubscriptBox["L", "X"], "[", + RowBox[{"x_", ",", "S_", ",", "\[Mu]_", ",", "\[Sigma]_"}], "]"}], " ", ":=", + " ", + FractionBox["x", + RowBox[{"1", " ", "-", " ", + RowBox[{"\[CapitalPhi]", "[", + RowBox[{ + SubscriptBox["d", "1"], "[", + RowBox[{"S", ",", "\[Mu]", ",", "\[Sigma]"}], "]"}], "]"}]}]]}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["L", "Y"], "[", + RowBox[{"y_", ",", "S_", ",", "\[Mu]_", ",", "\[Sigma]_"}], "]"}], " ", ":=", + " ", + FractionBox["y", + RowBox[{"\[Mu]", " ", + RowBox[{"\[CapitalPhi]", "[", + RowBox[{ + SubscriptBox["d", "2"], "[", + RowBox[{"S", ",", "\[Mu]", ",", "\[Sigma]"}], "]"}], "]"}]}]]}], "\n", + RowBox[{ + RowBox[{"X", "[", + RowBox[{"y_", ",", "S_", ",", "\[Mu]_", ",", "\[Sigma]_"}], "]"}], " ", ":=", + " ", + RowBox[{ + RowBox[{ + SubscriptBox["L", "Y"], "[", + RowBox[{"y", ",", "S", ",", "\[Mu]", ",", "\[Sigma]"}], "]"}], " ", + RowBox[{"(", + RowBox[{"1", " ", "-", " ", + RowBox[{"\[CapitalPhi]", "[", + RowBox[{ + SubscriptBox["d", "1"], "[", + RowBox[{"S", ",", "\[Mu]", ",", "\[Sigma]"}], "]"}], "]"}]}], + ")"}]}]}], "\n", + RowBox[{ + RowBox[{"Y", "[", + RowBox[{"x_", ",", "S_", ",", "\[Mu]_", ",", "\[Sigma]_"}], "]"}], " ", ":=", + " ", + RowBox[{"\[Mu]", " ", + RowBox[{ + SubscriptBox["L", "X"], "[", + RowBox[{"x", ",", "S", ",", "\[Mu]", ",", "\[Sigma]"}], "]"}], " ", + RowBox[{"\[CapitalPhi]", "[", + RowBox[{ + SubscriptBox["d", "2"], "[", + RowBox[{"S", ",", "\[Mu]", ",", "\[Sigma]"}], "]"}], "]"}]}]}]}], "Code",\ + + CellChangeTimes->{{3.911383688783895*^9, 3.911383741794456*^9}, { + 3.911383797950727*^9, 3.911383809912835*^9}, {3.9113838491740713`*^9, + 3.911383864952888*^9}, {3.9113841779644413`*^9, 3.911384322151863*^9}, { + 3.911384433609087*^9, 3.911384448745434*^9}, {3.9113850554248533`*^9, + 3.911385099649076*^9}, {3.91138525707533*^9, 3.911385263363533*^9}, { + 3.911385324670476*^9, 3.911385325035862*^9}, {3.911409187909778*^9, + 3.9114091880800257`*^9}, 3.9185709187863417`*^9}, + CellLabel-> + "In[3837]:=",ExpressionUUID->"3950a1e9-9c32-45c4-b5ef-404c37d6ef65"] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["\<\ +These are functions that are used to get prices from either a balance in X or \ +a balance in Y.\ +\>", "Subsubsection", + CellChangeTimes->{{3.91138394332069*^9, + 3.911383960427863*^9}},ExpressionUUID->"6228385e-cfd2-4bd0-97f4-\ +58c98a5a994e"], + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + SubscriptBox["P", "X"], "[", + RowBox[{"x_", ",", "L_", ",", "\[Mu]_", ",", "\[Sigma]_"}], "]"}], " ", ":=", + " ", + RowBox[{"\[Mu]", " ", + RowBox[{"Exp", "[", + RowBox[{ + RowBox[{ + RowBox[{ + SubscriptBox["\[CapitalPhi]", "inv"], "[", + RowBox[{"1", " ", "-", " ", + FractionBox["x", "L"]}], "]"}], "\[Sigma]"}], " ", "-", " ", + RowBox[{ + FractionBox["1", "2"], + SuperscriptBox["\[Sigma]", "2"]}]}], "]"}]}]}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["P", "Y"], "[", + RowBox[{"y_", ",", "L_", ",", "\[Mu]_", ",", "\[Sigma]_"}], "]"}], " ", ":=", + " ", + RowBox[{"\[Mu]", " ", + RowBox[{"Exp", "[", + RowBox[{ + RowBox[{ + RowBox[{ + SubscriptBox["\[CapitalPhi]", "inv"], "[", + FractionBox["y", + RowBox[{"\[Mu]", " ", "L"}]], "]"}], "\[Sigma]"}], " ", "+", " ", + RowBox[{ + FractionBox["1", "2"], + SuperscriptBox["\[Sigma]", "2"]}]}], "]"}]}]}]}], "Code", + CellChangeTimes->{{3.9113839769604807`*^9, 3.911384029460125*^9}, { + 3.911385062781126*^9, 3.911385091502931*^9}, 3.91857093005863*^9}, + CellLabel-> + "In[3841]:=",ExpressionUUID->"cdbca2c9-2426-4adf-8516-6c22e3b352b1"] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["Then we have the trading function", "Subsubsection", + CellChangeTimes->{{3.9114107980754547`*^9, + 3.911410804009096*^9}},ExpressionUUID->"18015876-38da-4fa9-82b7-\ +a417d745ef90"], + +Cell[BoxData[ + RowBox[{ + RowBox[{"\[CurlyPhi]", "[", + RowBox[{"x_", ",", "y_", ",", "L_", ",", "\[Mu]_", ",", "\[Sigma]_"}], + "]"}], " ", ":=", " ", + RowBox[{ + RowBox[{ + SubscriptBox["\[CapitalPhi]", "inv"], "[", + FractionBox["x", "L"], "]"}], "+", + RowBox[{ + SubscriptBox["\[CapitalPhi]", "inv"], "[", + FractionBox["y", + RowBox[{"\[Mu]", " ", "L"}]], "]"}], "+", "\[Sigma]"}]}]], "Code", + CellChangeTimes->{{3.911410806554799*^9, 3.911410882453505*^9}, { + 3.9114109468550673`*^9, 3.9114109866059113`*^9}, 3.9185709352254477`*^9}, + CellLabel-> + "In[3843]:=",ExpressionUUID->"a65cb5ec-cdac-40e0-bb49-f3d8157592d9"] +}, Open ]] +}, Open ]] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["\<\ +Let\[CloseCurlyQuote]s initialize a pool with some constants and some \ +liquidity. \ +\>", "Section", + CellChangeTimes->{{3.911384055956565*^9, 3.911384066810919*^9}, { + 3.911384711690135*^9, + 3.911384714277852*^9}},ExpressionUUID->"da815218-0c74-4720-a5c9-\ +76f45781c5e2"], + +Cell[CellGroupData[{ + +Cell["\<\ +First, let\[CloseCurlyQuote]s set the parameters for our curve, including the \ +fee parameter \[Gamma]\ +\>", "Subsection", + CellChangeTimes->{{3.911384725040826*^9, 3.911384731518064*^9}, { + 3.9113851982356*^9, + 3.911385205023809*^9}},ExpressionUUID->"d461a415-44ca-4804-8248-\ +6137a0f9449f"], + +Cell[CellGroupData[{ + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + RowBox[{ + RowBox[{"{", + RowBox[{ + SubscriptBox["\[Mu]", "0"], ",", " ", + SubscriptBox["\[Sigma]", "0"], ",", " ", + SubscriptBox["\[Gamma]", "0"]}], "}"}], " ", "=", " ", + RowBox[{"{", + RowBox[{"1", ",", " ", + FractionBox["1", "4"], ",", " ", "0.995"}], "}"}]}], ";"}], " "}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + SubscriptBox["K", "0"], ",", " ", + "\"\<\!\(\*SubscriptBox[\(K\), \(0\)]\) = \>\""}], "]"}], ";", " ", + RowBox[{"Echo", "[", + RowBox[{ + SubscriptBox["\[Sigma]", "0"], ",", " ", + "\"\<\!\(\*SubscriptBox[\(\[Sigma]\), \(0\)]\) = \>\""}], "]"}], ";", " ", + RowBox[{"Echo", "[", + RowBox[{ + SubscriptBox["\[Gamma]", "0"], ",", " ", + "\"\<\!\(\*SubscriptBox[\(\[Gamma]\), \(0\)]\) = \>\""}], "]"}], + ";"}]}], "Code", + CellChangeTimes->{ + 3.91138448247676*^9, {3.9113845136096563`*^9, 3.911384540517331*^9}, { + 3.911384579083336*^9, 3.911384646274679*^9}, {3.911384706054633*^9, + 3.911384732869401*^9}, {3.911385194438079*^9, 3.9113852384442177`*^9}, + 3.911387336640081*^9, {3.911408996136958*^9, 3.9114090092659817`*^9}, { + 3.9169341546962433`*^9, 3.916934155785771*^9}, {3.916934920973572*^9, + 3.916934921366069*^9}, {3.916935004865656*^9, 3.916935006671085*^9}, { + 3.916935097711054*^9, 3.916935099175394*^9}, {3.91693537100875*^9, + 3.916935393268579*^9}, {3.916935561687649*^9, 3.916935563633135*^9}, { + 3.91738367920582*^9, 3.917383680576692*^9}, {3.917384081819539*^9, + 3.917384085426984*^9}, {3.9173856229323063`*^9, 3.917385639341588*^9}, { + 3.91751517656183*^9, 3.917515189435231*^9}, {3.918570943201333*^9, + 3.918570984185614*^9}, {3.918571110139764*^9, 3.9185711113502274`*^9}, { + 3.9185720081337433`*^9, 3.918572008408592*^9}, {3.918575751023251*^9, + 3.918575751913149*^9}, {3.918576073288947*^9, 3.9185760735774183`*^9}, { + 3.918576642017666*^9, 3.918576642103425*^9}, {3.918582513027935*^9, + 3.918582513467471*^9}, {3.918582809143463*^9, 3.918582809217847*^9}, { + 3.918582876514236*^9, 3.918582923985523*^9}, {3.918583425222728*^9, + 3.918583425376048*^9}, {3.918583546078206*^9, 3.918583546573629*^9}, { + 3.918584395359364*^9, 3.918584395834972*^9}, {3.9185864064676332`*^9, + 3.918586406580247*^9}, {3.918587050341199*^9, 3.918587075189999*^9}, { + 3.918588451693397*^9, 3.918588451829137*^9}, {3.918630585921973*^9, + 3.918630585996049*^9}, {3.9186306784955587`*^9, 3.918630678602592*^9}, { + 3.918631219716024*^9, 3.918631219811838*^9}, {3.918631276745323*^9, + 3.9186312768635263`*^9}}, + CellLabel-> + "In[3844]:=",ExpressionUUID->"8d262a91-37a1-41fb-b1b3-ae8191b1ccda"], + +Cell[CellGroupData[{ + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"\\!\\(\\*SubscriptBox[\\(K\\), \\(0\\)]\\) = \"\>", + "EchoLabel"], " ", "1"}]], "Echo", + CellChangeTimes->{ + 3.9186459576704597`*^9},ExpressionUUID->"63fa2685-ead2-42c3-8894-\ +aa3d0f759278"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"\\!\\(\\*SubscriptBox[\\(\[Sigma]\\), \\(0\\)]\\) = \"\>", + "EchoLabel"], " ", + FractionBox["1", "4"]}]], "Echo", + CellChangeTimes->{ + 3.918645957703246*^9},ExpressionUUID->"ab767d72-08f9-45a3-8d7a-\ +62a93aac3a75"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"\\!\\(\\*SubscriptBox[\\(\[Gamma]\\), \\(0\\)]\\) = \"\>", + "EchoLabel"], " ", "0.995`"}]], "Echo", + CellChangeTimes->{ + 3.918645957713688*^9},ExpressionUUID->"865ae55a-ba52-403c-9134-\ +dd94b3a70090"] +}, Open ]] +}, Open ]] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["\<\ +Now, let\[CloseCurlyQuote]s set the initial liquidity by providing an amount \ +of X and a price S.\ +\>", "Subsection", + CellChangeTimes->{{3.9113847363117323`*^9, 3.911384755116337*^9}, { + 3.911384849366685*^9, + 3.9113848505774117`*^9}},ExpressionUUID->"a6874bc0-590c-4cb3-9d20-\ +f010a014a154"], + +Cell[CellGroupData[{ + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + RowBox[{ + RowBox[{"{", + RowBox[{ + SubscriptBox["x", "0"], ",", + SubscriptBox["S", "0"]}], "}"}], " ", "=", " ", + RowBox[{"{", + RowBox[{"1000000000", ",", " ", "0.75"}], "}"}]}], ";"}], " "}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + SubscriptBox["x", "0"], ",", " ", + "\"\\""}], "]"}], ";", " ", + RowBox[{"Echo", "[", + RowBox[{ + SubscriptBox["S", "0"], ",", " ", + "\"\<\!\(\*SubscriptBox[\(S\), \(0\)]\) = \>\""}], "]"}], ";"}]}], "Code",\ + + CellChangeTimes->{{3.911384757671178*^9, 3.911384806225191*^9}, { + 3.911386982132729*^9, 3.911386989059497*^9}, 3.91138734111685*^9, { + 3.9114090196649647`*^9, 3.9114090339697657`*^9}, {3.917383705204445*^9, + 3.917383705323718*^9}, {3.917383899939695*^9, 3.9173839018597183`*^9}, { + 3.917383976339484*^9, 3.917383976688408*^9}, 3.917384041262576*^9, { + 3.9173840921075373`*^9, 3.9173840922911777`*^9}, 3.917384525229738*^9, { + 3.917515198755231*^9, 3.917515199530878*^9}, {3.918575982190796*^9, + 3.918575982584321*^9}, {3.918582811121216*^9, 3.9185828112296124`*^9}, { + 3.918582880689728*^9, 3.9185828808310337`*^9}, {3.918582926936624*^9, + 3.9185829296473217`*^9}, {3.91858299719833*^9, 3.918582997406849*^9}, { + 3.918583291020159*^9, 3.918583291177719*^9}, {3.918583428982098*^9, + 3.9185834291087303`*^9}, {3.918583548048421*^9, 3.918583551706524*^9}, { + 3.918584158439363*^9, 3.918584158785482*^9}, {3.918584216906336*^9, + 3.9185842170754147`*^9}, {3.918584354834117*^9, 3.918584355139624*^9}, { + 3.918584446835289*^9, 3.918584447252163*^9}, {3.918585317499875*^9, + 3.918585320001732*^9}, {3.918585959285797*^9, 3.918585960380043*^9}, { + 3.918587011890151*^9, 3.9185870122509117`*^9}, {3.91858707864706*^9, + 3.918587079216874*^9}, {3.9186305834832478`*^9, 3.918630583772887*^9}, { + 3.918630681419763*^9, 3.918630681553543*^9}, {3.9186312811761227`*^9, + 3.9186312818709307`*^9}, 3.918631349127054*^9, {3.918631421837872*^9, + 3.918631422168438*^9}, {3.918631663100456*^9, 3.9186316633223667`*^9}, { + 3.918643343448288*^9, 3.918643343752068*^9}, {3.918646106269537*^9, + 3.918646106440174*^9}, {3.918646277573381*^9, 3.918646277908587*^9}, { + 3.9186465675649567`*^9, 3.918646568407236*^9}, {3.918647114579299*^9, + 3.9186471322198763`*^9}},ExpressionUUID->"085ca656-cc94-43cf-a8c6-\ +c116e23e4910"], + +Cell[CellGroupData[{ + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The initial X-reserve balance is: \ +\\!\\(\\*SubscriptBox[\\(x\\), \\(0\\)]\\) = \"\>", + "EchoLabel"], " ", "1000000000"}]], "Echo", + CellChangeTimes->{ + 3.918646568794178*^9},ExpressionUUID->"d46f0b65-6c97-4fd7-a377-\ +fd814b863b4a"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"\\!\\(\\*SubscriptBox[\\(S\\), \\(0\\)]\\) = \"\>", + "EchoLabel"], " ", "0.75`"}]], "Echo", + CellChangeTimes->{ + 3.918646568829261*^9},ExpressionUUID->"d830d4bf-eeeb-4bb6-a004-\ +32188869cd46"] +}, Open ]] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["\<\ +From this, let\[CloseCurlyQuote]s see what we will get for the initial amount \ +of Y and L.\ +\>", "Subsubsection", + CellChangeTimes->{{3.9113848345992517`*^9, + 3.91138485967836*^9}},ExpressionUUID->"d0f44536-b00f-4974-8c75-\ +dd65dd319645"], + +Cell[CellGroupData[{ + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + RowBox[{ + RowBox[{"{", + RowBox[{ + SubscriptBox["L", "0"], ",", " ", + SubscriptBox["y", "0"]}], "}"}], " ", "=", " ", + RowBox[{"{", + RowBox[{ + RowBox[{ + SubscriptBox["L", "X"], "[", + RowBox[{ + SubscriptBox["x", "0"], ",", + SubscriptBox["S", "0"], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"]}], "]"}], ",", " ", + RowBox[{"Y", "[", + RowBox[{ + SubscriptBox["x", "0"], ",", + SubscriptBox["S", "0"], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"]}], "]"}]}], "}"}]}], ";"}], " "}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"N", "[", + RowBox[{ + SubscriptBox["L", "0"], ",", " ", "18"}], "]"}], ",", " ", + "\"\\""}], "]"}], ";", " ", + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"N", "[", + RowBox[{ + SubscriptBox["y", "0"], ",", " ", "18"}], "]"}], ",", " ", + "\"\\""}], "]"}], ";"}]}], "Code", + CellChangeTimes->{{3.911384864489366*^9, 3.911384942301722*^9}, { + 3.911384992602735*^9, 3.9113850128379593`*^9}, {3.911385047347066*^9, + 3.9113850518014383`*^9}, {3.9113851385254107`*^9, 3.9113851753238697`*^9}, { + 3.91138696634296*^9, 3.911386992951726*^9}, {3.911387349621842*^9, + 3.911387398394403*^9}, {3.911912845565853*^9, 3.9119128460491123`*^9}, { + 3.918570990618823*^9, 3.918570994101475*^9}, {3.9185711059481163`*^9, + 3.9185711082874823`*^9}}, + CellLabel-> + "In[4060]:=",ExpressionUUID->"57112165-db6f-447c-9713-f9c85c6d4828"], + +Cell[CellGroupData[{ + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The initial liquidity is: \\!\\(\\*SubscriptBox[\\(L\\), \ +\\(0\\)]\\) = \"\>", + "EchoLabel"], " ", "1.1799546999753058`*^9"}]], "Echo", + CellChangeTimes->{ + 3.918646570423168*^9},ExpressionUUID->"d45d2202-bcd3-4d72-9040-\ +061ec9d3b3fd"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The initial Y-reserve balance is: \ +\\!\\(\\*SubscriptBox[\\(y\\), \\(0\\)]\\) = \"\>", + "EchoLabel"], " ", "1.1920585842721254`*^8"}]], "Echo", + CellChangeTimes->{ + 3.918646570454266*^9},ExpressionUUID->"7f4698d3-1c35-46e4-b14b-\ +549da3ca9c05"] +}, Open ]] +}, Open ]] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["Let\[CloseCurlyQuote]s check that the prices are correct after the \ +fact.", "Subsubsection", + CellChangeTimes->{{3.911385351666885*^9, + 3.91138536037985*^9}},ExpressionUUID->"0dabba71-0908-48dd-b11c-\ +9fc06ac2d79b"], + +Cell[CellGroupData[{ + +Cell[BoxData[{ + RowBox[{"Assert", "[", + RowBox[{ + RowBox[{ + SubscriptBox["P", "X"], "[", + RowBox[{ + SubscriptBox["x", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"]}], "]"}], " ", "==", " ", + RowBox[{ + SubscriptBox["P", "Y"], "[", + RowBox[{ + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"]}], "]"}]}], "]"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{ + SubscriptBox["P", "X"], "[", + RowBox[{ + SubscriptBox["x", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"]}], "]"}], ",", " ", + "\"\\""}], "]"}], ";"}], " "}]}], "Code", + CellChangeTimes->{{3.9113853639105387`*^9, 3.9113854185634947`*^9}, { + 3.9113869483534203`*^9, 3.911386962492029*^9}, {3.911386994813387*^9, + 3.911387177122032*^9}, {3.911387291516371*^9, 3.911387309281919*^9}, + 3.911912928324705*^9, {3.9185711812613907`*^9, 3.918571185390046*^9}, { + 3.91857169317227*^9, 3.918571699558569*^9}}, + CellLabel-> + "In[4062]:=",ExpressionUUID->"05df5500-1e66-49a9-afd7-256365f8b6dc"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The initial price is: P = \"\>", + "EchoLabel"], " ", "0.75`"}]], "Echo", + CellChangeTimes->{ + 3.9186465723885307`*^9},ExpressionUUID->"2780423d-6264-4b5d-a5a5-\ +68bad0b82ef8"] +}, Open ]] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["\<\ +Just to verify that we could have done this the other way, and show the flow, \ +let\[CloseCurlyQuote]s do that real fast.\ +\>", "Subsubsection", + CellChangeTimes->{{3.9113853013290577`*^9, + 3.9113853161239634`*^9}},ExpressionUUID->"6c78f375-83c5-40c5-a26f-\ +9dfafb83427b"], + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + RowBox[{"{", + RowBox[{ + SubscriptBox["L", + SubscriptBox["0", "y"]], ",", " ", + SubscriptBox["x", + SubscriptBox["0", "y"]]}], "}"}], " ", "=", " ", + RowBox[{"{", + RowBox[{ + RowBox[{ + SubscriptBox["L", "Y"], "[", + RowBox[{ + SubscriptBox["y", "0"], ",", + SubscriptBox["S", "0"], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"]}], "]"}], ",", " ", + RowBox[{"X", "[", + RowBox[{ + SubscriptBox["y", "0"], ",", + SubscriptBox["S", "0"], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"]}], "]"}]}], "}"}]}], ";"}], "\n", + RowBox[{ + RowBox[{"Assert", "[", + RowBox[{ + RowBox[{"{", + RowBox[{ + SubscriptBox["L", + SubscriptBox["0", "y"]], ",", + SubscriptBox["x", + SubscriptBox["0", "y"]]}], "}"}], " ", "==", " ", + RowBox[{"{", + RowBox[{ + SubscriptBox["L", "0"], ",", + SubscriptBox["x", "0"]}], "}"}]}], "]"}], ";"}]}], "Code", + CellChangeTimes->{{3.911387447754344*^9, 3.911387533259612*^9}, { + 3.911409113951961*^9, 3.911409136342054*^9}, {3.911409206127439*^9, + 3.911409206365666*^9}, {3.918571188362344*^9, 3.918571190424653*^9}, { + 3.9185717026957493`*^9, 3.918571706012089*^9}}, + CellLabel-> + "In[4064]:=",ExpressionUUID->"05cdeb1a-e1b0-40a7-af51-51ab9b75cc2e"] +}, Open ]] +}, Open ]] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["Swapping", "Section", + CellChangeTimes->{{3.911387861088531*^9, + 3.9113878701923523`*^9}},ExpressionUUID->"e0558e89-2c12-471a-af64-\ +7b6a1457eb4d"], + +Cell[CellGroupData[{ + +Cell["\<\ +Now we need to set up the swap logic. We will use R to denote an arbitrary \ +reserve.\ +\>", "Subsection", + CellChangeTimes->{{3.911387873703059*^9, 3.911387883191267*^9}, { + 3.911388058682213*^9, + 3.911388067027336*^9}},ExpressionUUID->"2f7348b3-443c-4a22-943e-\ +943b7962999e"], + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + RowBox[{"fees", "[", + RowBox[{"\[CapitalDelta]_", ",", "\[Gamma]_"}], "]"}], " ", ":=", " ", + RowBox[{ + RowBox[{"(", + RowBox[{"1", "-", "\[Gamma]"}], ")"}], "\[CapitalDelta]"}]}], + " "}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["\[Delta]", "LiqX"], "[", + RowBox[{ + "\[CapitalDelta]_", ",", "x_", ",", "y_", ",", "L_", ",", "\[Mu]_", ",", + "\[Sigma]_", ",", "\[Gamma]_"}], "]"}], " ", ":=", " ", + RowBox[{ + RowBox[{ + SubscriptBox["P", "X"], "[", + RowBox[{"x", ",", "L", ",", "\[Mu]", ",", "\[Sigma]"}], "]"}], " ", "L", + " ", + FractionBox[ + RowBox[{"fees", "[", + RowBox[{"\[CapitalDelta]", ",", "\[Gamma]"}], "]"}], + RowBox[{ + RowBox[{ + RowBox[{ + SubscriptBox["P", "X"], "[", + RowBox[{"x", ",", "L", ",", "\[Mu]", ",", "\[Sigma]"}], "]"}], " ", + "x"}], " ", "+", " ", "y"}]]}]}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["\[Delta]", "LiqY"], "[", + RowBox[{ + "\[CapitalDelta]_", ",", "x_", ",", "y_", ",", "L_", ",", "\[Mu]_", ",", + "\[Sigma]_", ",", "\[Gamma]_"}], "]"}], " ", ":=", " ", + RowBox[{"L", " ", + FractionBox[ + RowBox[{"fees", "[", + RowBox[{"\[CapitalDelta]", ",", "\[Gamma]"}], "]"}], + RowBox[{ + RowBox[{ + RowBox[{ + SubscriptBox["P", "Y"], "[", + RowBox[{"y", ",", "L", ",", "\[Mu]", ",", "\[Sigma]"}], "]"}], " ", + "x"}], " ", "+", " ", "y"}]]}]}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["\[CapitalDelta]", "X"], "[", + RowBox[{ + "\[CapitalDelta]_", ",", "x_", ",", "y_", ",", "L_", ",", "\[Mu]_", ",", + "\[Sigma]_", ",", "\[Gamma]_"}], "]"}], " ", ":=", " ", + RowBox[{ + RowBox[{ + RowBox[{"(", + RowBox[{"L", "+", + RowBox[{ + SubscriptBox["\[Delta]", "LiqY"], "[", + RowBox[{ + "\[CapitalDelta]", ",", "x", ",", "y", ",", "L", ",", "\[Mu]", ",", + "\[Sigma]", ",", "\[Gamma]"}], "]"}]}], ")"}], + RowBox[{"\[CapitalPhi]", "[", + RowBox[{ + RowBox[{"-", "\[Sigma]"}], " ", "-", " ", + RowBox[{ + SubscriptBox["\[CapitalPhi]", "inv"], "[", + FractionBox[ + RowBox[{"y", "+", "\[CapitalDelta]"}], + RowBox[{"\[Mu]", + RowBox[{"(", + RowBox[{"L", "+", + RowBox[{ + SubscriptBox["\[Delta]", "LiqY"], "[", + RowBox[{ + "\[CapitalDelta]", ",", "x", ",", "y", ",", "L", ",", "\[Mu]", + ",", "\[Sigma]", ",", "\[Gamma]"}], "]"}]}], ")"}]}]], "]"}]}], + "]"}]}], "-", "x"}]}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["\[CapitalDelta]", "Y"], "[", + RowBox[{ + "\[CapitalDelta]_", ",", "x_", ",", "y_", ",", "L_", ",", "\[Mu]_", ",", + "\[Sigma]_", ",", "\[Gamma]_"}], "]"}], " ", ":=", " ", + RowBox[{ + RowBox[{"\[Mu]", + RowBox[{"(", + RowBox[{"L", "+", + RowBox[{ + SubscriptBox["\[Delta]", "LiqX"], "[", + RowBox[{ + "\[CapitalDelta]", ",", "x", ",", "y", ",", "L", ",", "\[Mu]", ",", + "\[Sigma]", ",", "\[Gamma]"}], "]"}]}], ")"}], + RowBox[{"\[CapitalPhi]", "[", + RowBox[{ + RowBox[{"-", "\[Sigma]"}], " ", "-", " ", + RowBox[{ + SubscriptBox["\[CapitalPhi]", "inv"], "[", + FractionBox[ + RowBox[{"x", "+", "\[CapitalDelta]"}], + RowBox[{"L", "+", + RowBox[{ + SubscriptBox["\[Delta]", "LiqX"], "[", + RowBox[{ + "\[CapitalDelta]", ",", "x", ",", "y", ",", "L", ",", "\[Mu]", ",", + "\[Sigma]", ",", "\[Gamma]"}], "]"}]}]], "]"}]}], "]"}]}], "-", + "y"}]}]}], "Code", + CellChangeTimes->{{3.9113879019426517`*^9, 3.91138810608572*^9}, { + 3.911388182414874*^9, 3.911388482660475*^9}, {3.911388787674255*^9, + 3.911388979350903*^9}, {3.911389141187207*^9, 3.911389159323999*^9}, { + 3.911390630736039*^9, 3.911390698837728*^9}, {3.911408926773992*^9, + 3.911408926935534*^9}, {3.916935312155489*^9, 3.916935312305293*^9}, { + 3.9173824759156227`*^9, 3.917382476601667*^9}, {3.91738376350004*^9, + 3.917383784452558*^9}, {3.917383943744508*^9, 3.917383954282363*^9}, { + 3.9173839922487173`*^9, 3.9173839928360653`*^9}, {3.9173844534077587`*^9, + 3.91738450986141*^9}, {3.917384708602298*^9, 3.917384711107472*^9}, { + 3.917384741504918*^9, 3.917384745117815*^9}, {3.917384781098084*^9, + 3.917384801570612*^9}, {3.917384922013281*^9, 3.917384925581356*^9}, { + 3.9173850860945387`*^9, 3.917385090363779*^9}, 3.917385467785697*^9, { + 3.91857109514256*^9, 3.9185711020502167`*^9}, {3.91857136201642*^9, + 3.918571379355769*^9}, {3.9185719508515673`*^9, 3.9185719564304523`*^9}, { + 3.918572032513034*^9, 3.9185720372819643`*^9}, {3.918575843943832*^9, + 3.918575846003868*^9}, {3.918576114999403*^9, 3.9185761615915833`*^9}, { + 3.918576659877521*^9, 3.918576669588545*^9}, {3.918576871025722*^9, + 3.918576980060952*^9}, {3.9185799351979227`*^9, 3.918580034351376*^9}, { + 3.918580066820713*^9, 3.918580088205531*^9}, {3.91858024962215*^9, + 3.918580262523444*^9}, {3.918580294205887*^9, 3.918580298495079*^9}, { + 3.918580365884131*^9, 3.9185803726739683`*^9}, {3.9185815280229187`*^9, + 3.918581595304048*^9}, {3.918581637999057*^9, 3.9185817030183477`*^9}, { + 3.9185818429293957`*^9, 3.9185818876898746`*^9}, {3.9185823204024343`*^9, + 3.918582435530633*^9}, {3.918582488030525*^9, 3.918582492481691*^9}, { + 3.918582676185212*^9, 3.918582684377091*^9}, {3.9185827212029133`*^9, + 3.918582726305999*^9}, {3.918582845001643*^9, 3.918582856707839*^9}, { + 3.91858350972567*^9, 3.918583524907681*^9}, {3.9185836349151993`*^9, + 3.918583689775346*^9}, {3.918583814649831*^9, 3.918583887826089*^9}, + 3.918584019823391*^9, {3.918584115383397*^9, 3.918584125997015*^9}, { + 3.918584188749694*^9, 3.918584207807349*^9}, {3.918584252596579*^9, + 3.918584283333911*^9}, {3.9185843240947323`*^9, 3.9185843419430447`*^9}, { + 3.918584596408697*^9, 3.918584605431155*^9}, {3.918584698005369*^9, + 3.9185847022659883`*^9}, {3.918584733166923*^9, 3.9185847359196243`*^9}, { + 3.918584835029223*^9, 3.918584914920583*^9}, {3.918585923120076*^9, + 3.918585949059307*^9}, {3.918585981929215*^9, 3.918586212982409*^9}, { + 3.9185862713877707`*^9, 3.918586283220223*^9}, {3.918586430146008*^9, + 3.918586438229198*^9}, {3.91858647808181*^9, 3.9185865516402187`*^9}, { + 3.9185869770915956`*^9, 3.918586998684773*^9}, {3.9185871705919724`*^9, + 3.918587233694872*^9}, {3.918587688250499*^9, 3.918587732617826*^9}, { + 3.918587822943163*^9, 3.918587895921957*^9}, {3.918630539218589*^9, + 3.9186305548276157`*^9}, {3.918630656016925*^9, 3.918630658939457*^9}, { + 3.918630702505906*^9, 3.918630723206217*^9}, {3.918630766067203*^9, + 3.9186307800116253`*^9}, {3.918630857798958*^9, 3.918630861330348*^9}, { + 3.918630944696974*^9, 3.9186309775688953`*^9}, {3.918631062524468*^9, + 3.918631190734248*^9}, {3.9186313271535597`*^9, 3.91863134257788*^9}, { + 3.918631387338098*^9, 3.918631390137829*^9}, {3.918631437297716*^9, + 3.918631438126658*^9}, {3.918631612143648*^9, 3.9186316513861513`*^9}, { + 3.918631686728327*^9, 3.918631830046607*^9}, {3.918631896508091*^9, + 3.9186320069107533`*^9}, {3.91863303862149*^9, 3.9186330635458193`*^9}, { + 3.91863310072386*^9, 3.918633119499754*^9}, {3.918633166361033*^9, + 3.918633196016712*^9}, {3.9186332409230433`*^9, 3.918633311599324*^9}, { + 3.918633342234268*^9, 3.918633383646895*^9}, {3.918633455651833*^9, + 3.9186334734597473`*^9}, {3.9186432538684263`*^9, 3.918643296703499*^9}, { + 3.9186433525370207`*^9, 3.918643372417371*^9}, 3.918643647132059*^9, { + 3.9186456605015583`*^9, 3.918645704439562*^9}, {3.918645736019648*^9, + 3.9186457417525*^9}, {3.9186458580141783`*^9, 3.9186458880641613`*^9}, { + 3.918646011039798*^9, 3.9186460131439123`*^9}, {3.918646045288979*^9, + 3.918646059600195*^9}, {3.91864617045295*^9, 3.918646267932062*^9}, { + 3.9186464192975893`*^9, 3.91864641960671*^9}, {3.918646608483427*^9, + 3.91864684851731*^9}, {3.9186469185057583`*^9, 3.918647050242887*^9}, { + 3.918647093303738*^9, 3.918647093440069*^9}, {3.918647162357498*^9, + 3.9186471626747093`*^9}}, + CellLabel-> + "In[4174]:=",ExpressionUUID->"0716117c-5381-46be-8b57-f2cefa727879"], + +Cell[CellGroupData[{ + +Cell[BoxData[{ + RowBox[{ + RowBox[{"YIn", " ", "=", " ", "1"}], ";"}], "\n", + RowBox[{ + RowBox[{"XOut", " ", "=", " ", + RowBox[{ + SubscriptBox["\[CapitalDelta]", "X"], "[", + RowBox[{"YIn", ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}], ";"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{"XOut", ",", " ", "\"\\""}], "]"}], ";"}], " ", + RowBox[{"(*", " ", + RowBox[{"Should", " ", "be", " ", ".796"}], " ", "*)"}]}], "\n", + RowBox[{ + RowBox[{"DeltaL", " ", "=", " ", + RowBox[{ + SubscriptBox["\[Delta]", "LiqY"], "[", + RowBox[{"YIn", ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}], ";"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"\[CurlyPhi]", "[", + RowBox[{ + RowBox[{ + SubscriptBox["x", "0"], "+", "XOut"}], ",", + RowBox[{ + SubscriptBox["y", "0"], "+", "YIn"}], ",", + RowBox[{ + SubscriptBox["L", "0"], "+", "DeltaL"}], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"]}], "]"}], ",", " ", + "\"\\""}], "]"}], ";"}], "\n"}], "\n", + RowBox[{ + RowBox[{"XIn", " ", "=", " ", "1"}], ";"}], "\n", + RowBox[{ + RowBox[{"YOut", " ", "=", " ", + RowBox[{ + SubscriptBox["\[CapitalDelta]", "Y"], "[", + RowBox[{"XIn", ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}], ";"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{"YOut", ",", " ", "\"\\""}], "]"}], ";"}], " ", + RowBox[{"(*", " ", + RowBox[{"Should", " ", "be", " ", "1.24375"}], " ", "*)"}]}], "\n", + RowBox[{ + RowBox[{"DeltaL", " ", "=", " ", + RowBox[{ + SubscriptBox["\[Delta]", "LiqX"], "[", + RowBox[{"XIn", ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}], ";"}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"\[CurlyPhi]", "[", + RowBox[{ + RowBox[{ + SubscriptBox["x", "0"], "+", "XIn"}], ",", + RowBox[{ + SubscriptBox["y", "0"], "+", "YOut"}], ",", + RowBox[{ + SubscriptBox["L", "0"], "+", "DeltaL"}], ",", + SubscriptBox["\[Mu]", "0"], ",", + SubscriptBox["\[Sigma]", "0"]}], "]"}], ",", " ", + "\"\\""}], "]"}], ";"}]}], "Code", + CellChangeTimes->{{3.9173824881246443`*^9, 3.917382640107789*^9}, { + 3.917382703512905*^9, 3.917382797160448*^9}, {3.917382957143201*^9, + 3.9173829787289667`*^9}, {3.917383009866159*^9, 3.91738301413713*^9}, { + 3.9173830595966473`*^9, 3.9173830608297663`*^9}, {3.917383133177814*^9, + 3.917383139655548*^9}, {3.91738319090906*^9, 3.9173832152140627`*^9}, { + 3.917383329695533*^9, 3.9173833441047373`*^9}, {3.917383592136142*^9, + 3.917383601902347*^9}, {3.917383788771337*^9, 3.9173838368765507`*^9}, { + 3.917383998912529*^9, 3.917384002782464*^9}, {3.917384361112628*^9, + 3.917384432792912*^9}, {3.917384584058999*^9, 3.917384619281192*^9}, { + 3.9173846522116756`*^9, 3.9173846873539762`*^9}, {3.9173849883607492`*^9, + 3.917384990884163*^9}, {3.917385341574011*^9, 3.917385423584772*^9}, { + 3.918571382615984*^9, 3.918571412216847*^9}, {3.918571717856246*^9, + 3.918571718925044*^9}, {3.918571975672353*^9, 3.9185719781640577`*^9}, { + 3.918575850485878*^9, 3.918575871098755*^9}, {3.918580315169186*^9, + 3.9185803368583097`*^9}, {3.918582534732585*^9, 3.9185825771962633`*^9}, { + 3.918582688010583*^9, 3.9185826955344*^9}, {3.918582731539423*^9, + 3.9185827367343893`*^9}, {3.918582864604357*^9, 3.918582868781686*^9}, { + 3.918583482655109*^9, 3.918583500351995*^9}, 3.9185843702651167`*^9, { + 3.918584645054994*^9, 3.9185846751565027`*^9}, {3.918584720161016*^9, + 3.918584725365926*^9}, {3.918587956963675*^9, 3.918587962241294*^9}, + 3.918589138602861*^9, 3.9186320307912188`*^9, {3.9186331433380632`*^9, + 3.9186331543138247`*^9}, {3.9186468606918497`*^9, + 3.9186469067855988`*^9}, {3.918647072837221*^9, 3.918647073727294*^9}, { + 3.918647266809247*^9, 3.918647298407833*^9}}, + CellLabel-> + "In[4199]:=",ExpressionUUID->"2687c350-1150-4951-bcf0-28364fa5bf73"], + +Cell[CellGroupData[{ + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"XOut = \"\>", + "EchoLabel"], " ", + RowBox[{"-", "1.3266667127609253`"}]}]], "Echo", + CellChangeTimes->{ + 3.9186472988237343`*^9},ExpressionUUID->"27baf199-5392-4622-872b-\ +c04b2522ee2d"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"Validation = \"\>", + "EchoLabel"], " ", + RowBox[{"-", "2.220446049250313`*^-16"}]}]], "Echo", + CellChangeTimes->{ + 3.91864729885005*^9},ExpressionUUID->"eecd11fc-1f1d-4de9-9e4a-07d53c8e0fbb"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"YOut = \"\>", + "EchoLabel"], " ", + RowBox[{"-", "0.7462499886751175`"}]}]], "Echo", + CellChangeTimes->{ + 3.91864729885777*^9},ExpressionUUID->"292d880d-854e-49ac-80b7-b67aa90a72e5"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"Validation = \"\>", + "EchoLabel"], " ", + RowBox[{"-", "2.220446049250313`*^-16"}]}]], "Echo", + CellChangeTimes->{ + 3.9186472988780203`*^9},ExpressionUUID->"38d83234-0ac4-4733-b57c-\ +347036548a09"] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["Arbitrage", "Section", + CellChangeTimes->{{3.91138759286382*^9, 3.9113876060914783`*^9}, { + 3.911387637205267*^9, + 3.911387638219921*^9}},ExpressionUUID->"65d2b88d-22b2-481a-935c-\ +71bdce9f21f1"], + +Cell[TextData[{ + "We will assume there is some external price ", + Cell[BoxData[ + FormBox[ + SubscriptBox["S", "ext"], TraditionalForm]],ExpressionUUID-> + "cd8c38a7-d91f-4b2c-a735-67538238c600"], + "that we are given and decide whether or not to perform an arbitrage and, if \ +so, to get the optimal trade size. That is, the trade that gives the \ +arbitrageur maximal profit." +}], "Text", + CellChangeTimes->{{3.9113876414292507`*^9, 3.9113877131079063`*^9}, { + 3.911388488238481*^9, + 3.911388489476202*^9}},ExpressionUUID->"f20cba79-bbb2-4d1b-9349-\ +a349e8b0b7a4"], + +Cell[CellGroupData[{ + +Cell[TextData[{ + "We will need the marginal price ", + Cell[BoxData[ + FormBox[ + SubscriptBox["P", "M"], TraditionalForm]],ExpressionUUID-> + "17089434-c32f-45ea-a26f-0d3ed9139695"], + " of a swap to compute optimal arbitrages and a profit calculation ", + Cell[BoxData[ + FormBox[ + SubscriptBox["V", "A"], TraditionalForm]],ExpressionUUID-> + "72aada08-6991-4530-9774-3dd0a302c916"] +}], "Subsubsection", + CellChangeTimes->{{3.9113884927083406`*^9, 3.911388517540121*^9}, { + 3.911388583492565*^9, 3.911388585552403*^9}, {3.9113886383414087`*^9, + 3.911388651317487*^9}},ExpressionUUID->"d7131855-64d1-451f-98f3-\ +152b4aa9f3a5"], + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + SubscriptBox["P", "M"], "[", + RowBox[{"dX_", ",", "dY_"}], "]"}], " ", ":=", " ", + FractionBox[ + RowBox[{"-", "dY"}], "dX"]}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["V", "A"], "[", + RowBox[{"Pm_", ",", "Pext_", ",", "\[CapitalDelta]_"}], "]"}], " ", ":=", + " ", + RowBox[{ + RowBox[{"(", + RowBox[{"Pm", " ", "-", " ", "Pext"}], ")"}], + "\[CapitalDelta]"}]}]}], "Code", + CellChangeTimes->{{3.911388520734275*^9, 3.911388754553933*^9}, { + 3.911408038142099*^9, 3.9114080663589067`*^9}, {3.9114081240293818`*^9, + 3.91140812426967*^9}}, + CellLabel-> + "In[2448]:=",ExpressionUUID->"fadd5b7c-a968-401d-9e42-d6ca30b3a2a6"] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["Lower External Price:", "Subsection", + CellChangeTimes->{{3.911387716836122*^9, 3.9113877303598022`*^9}, { + 3.911389282458209*^9, 3.911389285697858*^9}, {3.9119143659239197`*^9, + 3.911914366787615*^9}},ExpressionUUID->"eed8b815-af5d-458f-8082-\ +a956f0fb88b9"], + +Cell[CellGroupData[{ + +Cell[TextData[{ + "We\[CloseCurlyQuote]ll let ", + Cell[BoxData[ + FormBox[ + SubscriptBox["O", "X"], TraditionalForm]],ExpressionUUID-> + "bea8f527-9f1f-453d-b766-3832796355bb"], + " be the optimal amount of X token to tender to achieve maximal arbitrage \ +profit." +}], "Subsubsection", + CellChangeTimes->{{3.911389287836197*^9, 3.911389310066874*^9}, { + 3.911389351175222*^9, + 3.911389351682972*^9}},ExpressionUUID->"b20dcf41-6dfe-404c-8167-\ +50b070efca5f"], + +Cell[CellGroupData[{ + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + SubscriptBox["S", "ext"], " ", "=", " ", ".70"}], ";", " ", + RowBox[{"Assert", "[", + RowBox[{ + SubscriptBox["S", "ext"], " ", "<", " ", + SubscriptBox["S", "0"]}], "]"}], ";"}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["Prof", "Lower"], "[", "in_", "]"}], " ", ":=", " ", + RowBox[{ + SubscriptBox["V", "A"], "[", + RowBox[{ + RowBox[{ + SubscriptBox["P", "M"], "[", + RowBox[{"in", ",", + RowBox[{ + SubscriptBox["\[CapitalDelta]", "Y"], "[", + RowBox[{"in", ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["K", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Tau]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}], "]"}], ",", " ", + SubscriptBox["S", "ext"], ",", " ", "in"}], "]"}]}], "\n", + RowBox[{"Plot", "[", + RowBox[{ + RowBox[{ + SubscriptBox["Prof", "Lower"], "[", "v", "]"}], ",", " ", + RowBox[{"{", + RowBox[{"v", ",", "0", ",", "0.2"}], "}"}]}], "]"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{ + SubscriptBox["O", "X"], " ", "=", " ", + RowBox[{"ArgMax", "[", + RowBox[{ + RowBox[{"{", + RowBox[{ + RowBox[{ + SubscriptBox["Prof", "Lower"], "[", "x", "]"}], ",", " ", + RowBox[{"0", "<=", "x", "<=", + RowBox[{ + SubscriptBox["L", "0"], "-", + SubscriptBox["x", "0"]}]}]}], "}"}], ",", " ", "x"}], "]"}]}], + ";"}], " "}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + SubscriptBox["O", "X"], ",", " ", + "\"\\""}], "]"}], ";"}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"N", "[", + RowBox[{ + RowBox[{ + SubscriptBox["\[CapitalDelta]", "Y"], "[", + RowBox[{ + SubscriptBox["O", "X"], ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["K", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Tau]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}], ",", "18"}], "]"}], ",", " ", + "\"\\""}], "]"}], ";"}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"{", + RowBox[{ + RowBox[{ + SubscriptBox["x", "0"], " ", "+", " ", + SubscriptBox["O", "X"]}], ",", " ", + RowBox[{ + SubscriptBox["y", "0"], " ", "+", " ", + RowBox[{ + SubscriptBox["\[CapitalDelta]", "Y"], "[", + RowBox[{ + SubscriptBox["O", "X"], ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["K", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Tau]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}]}], "}"}], ",", " ", + "\"\\""}], "]"}], ";"}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["P", "F"], " ", "=", " ", + RowBox[{ + SubscriptBox["P", "X"], "[", + RowBox[{ + RowBox[{ + SubscriptBox["x", "0"], " ", "+", " ", + SubscriptBox["O", "X"]}], ",", " ", + RowBox[{ + SubscriptBox["L", "0"], " ", "+", " ", + RowBox[{ + SubscriptBox["\[Delta]", "Liq"], "[", + RowBox[{ + SubscriptBox["O", "X"], ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}], ",", " ", + SubscriptBox["K", "0"], ",", " ", + SubscriptBox["\[Sigma]", "0"], ",", " ", + SubscriptBox["\[Tau]", "0"]}], "]"}]}], ";", " ", + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"N", "[", + RowBox[{ + SubscriptBox["P", "F"], ",", "18"}], "]"}], ",", " ", + "\"\\""}], "]"}], ";"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"FullSimplify", "[", + RowBox[{"D", "[", + RowBox[{ + RowBox[{ + SubscriptBox["V", "A"], "[", + RowBox[{ + RowBox[{ + SubscriptBox["P", "M"], "[", + RowBox[{"v", ",", + RowBox[{ + SubscriptBox["\[CapitalDelta]", "Y"], "[", + RowBox[{ + "v", ",", "x", ",", "y", ",", "L", ",", "K", ",", "\[Sigma]", + ",", "\[Tau]", ",", "\[Gamma]"}], "]"}]}], "]"}], ",", " ", "S", + ",", " ", "v"}], "]"}], ",", "v"}], "]"}], "]"}], ",", " ", + "\"\\""}], "]"}], ";"}], + "\n", + RowBox[{"(*", " ", + RowBox[{ + "Check", " ", "that", " ", "the", " ", "trading", " ", "function", " ", + "is", " ", "invariant", " ", "under", " ", "the", " ", "swap"}], " ", + "*)"}]}], "\n", + RowBox[{ + RowBox[{"Assert", "[", + RowBox[{ + RowBox[{"Abs", "[", + RowBox[{"\[CurlyPhi]", "[", + RowBox[{ + RowBox[{ + SubscriptBox["x", "0"], " ", "+", " ", + SubscriptBox["O", "X"]}], ",", " ", + RowBox[{ + SubscriptBox["y", "0"], " ", "+", " ", + RowBox[{ + SubscriptBox["\[CapitalDelta]", "Y"], "[", + RowBox[{ + SubscriptBox["O", "X"], ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["K", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Tau]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}], ",", " ", + RowBox[{ + SubscriptBox["L", "0"], " ", "+", " ", + RowBox[{ + SubscriptBox["\[Delta]", "in"], "[", + RowBox[{ + SubscriptBox["O", "X"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}], ",", " ", + SubscriptBox["K", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Tau]", "0"]}], "]"}], "]"}], " ", "<", " ", + SuperscriptBox["10", + RowBox[{"-", "15"}]]}], "]"}], "\n"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{"checkLower", "[", + RowBox[{ + "v_", ",", "x_", ",", "y_", ",", "L_", ",", "S_", ",", "K_", ",", + "\[Sigma]_", ",", "\[Tau]_", ",", "\[Gamma]_"}], "]"}], ":=", + RowBox[{ + RowBox[{"-", "S"}], "+", + FractionBox[ + RowBox[{ + SuperscriptBox["\[ExponentialE]", + RowBox[{ + RowBox[{"-", + FractionBox[ + RowBox[{ + SuperscriptBox["\[Sigma]", "2"], " ", "\[Tau]"}], "2"]}], "+", + RowBox[{ + SqrtBox["2"], " ", "\[Sigma]", " ", + SqrtBox["\[Tau]"], " ", + RowBox[{"InverseErfc", "[", + FractionBox[ + RowBox[{"2", " ", "x", " ", + RowBox[{"(", + RowBox[{"v", "+", "x"}], ")"}]}], + RowBox[{"L", " ", + RowBox[{"(", + RowBox[{"v", "+", "x", "-", + RowBox[{"v", " ", "\[Gamma]"}]}], ")"}]}]], "]"}]}]}]], " ", + "K", " ", "x", " ", "\[Gamma]"}], + RowBox[{"v", "+", "x", "-", + RowBox[{"v", " ", "\[Gamma]"}]}]], "+", + FractionBox[ + RowBox[{"K", " ", "L", " ", + RowBox[{"(", + RowBox[{ + RowBox[{"-", "1"}], "+", "\[Gamma]"}], ")"}], " ", + RowBox[{"Erfc", "[", + RowBox[{ + FractionBox[ + RowBox[{"\[Sigma]", " ", + SqrtBox["\[Tau]"]}], + SqrtBox["2"]], "-", + RowBox[{"InverseErfc", "[", + FractionBox[ + RowBox[{"2", " ", "x", " ", + RowBox[{"(", + RowBox[{"v", "+", "x"}], ")"}]}], + RowBox[{"L", " ", + RowBox[{"(", + RowBox[{"v", "+", "x", "-", + RowBox[{"v", " ", "\[Gamma]"}]}], ")"}]}]], "]"}]}], "]"}]}], + RowBox[{"2", " ", "x"}]]}]}], ";"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{"numOne", "[", + RowBox[{ + "v_", ",", "x_", ",", "y_", ",", "L_", ",", "S_", ",", "K_", ",", + "\[Sigma]_", ",", "\[Tau]_", ",", "\[Gamma]_"}], "]"}], ":=", + RowBox[{ + SuperscriptBox["\[ExponentialE]", + RowBox[{ + RowBox[{"-", + FractionBox[ + RowBox[{ + SuperscriptBox["\[Sigma]", "2"], " ", "\[Tau]"}], "2"]}], "+", + RowBox[{ + SqrtBox["2"], " ", "\[Sigma]", " ", + SqrtBox["\[Tau]"], " ", + RowBox[{"InverseErfc", "[", + FractionBox[ + RowBox[{"2", " ", "x", " ", + RowBox[{"(", + RowBox[{"v", "+", "x"}], ")"}]}], + RowBox[{"L", " ", + RowBox[{"(", + RowBox[{"v", "+", "x", "-", + RowBox[{"v", " ", "\[Gamma]"}]}], ")"}]}]], "]"}]}]}]], " ", "K", + " ", "x", " ", "\[Gamma]"}]}], ";"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{"denomOne", "[", + RowBox[{ + "v_", ",", "x_", ",", "y_", ",", "L_", ",", "S_", ",", "K_", ",", + "\[Sigma]_", ",", "\[Tau]_", ",", "\[Gamma]_"}], "]"}], ":=", + RowBox[{"v", "+", "x", "-", + RowBox[{"v", " ", "\[Gamma]"}]}]}], ";"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{"numTwo", "[", + RowBox[{ + "v_", ",", "x_", ",", "y_", ",", "L_", ",", "S_", ",", "K_", ",", + "\[Sigma]_", ",", "\[Tau]_", ",", "\[Gamma]_"}], "]"}], ":=", + RowBox[{"K", " ", "L", " ", + RowBox[{"(", + RowBox[{ + RowBox[{"-", "1"}], "+", "\[Gamma]"}], ")"}], " ", + RowBox[{"Erfc", "[", + RowBox[{ + FractionBox[ + RowBox[{"\[Sigma]", " ", + SqrtBox["\[Tau]"]}], + SqrtBox["2"]], "-", + RowBox[{"InverseErfc", "[", + FractionBox[ + RowBox[{"2", " ", "x", " ", + RowBox[{"(", + RowBox[{"v", "+", "x"}], ")"}]}], + RowBox[{"L", " ", + RowBox[{"(", + RowBox[{"v", "+", "x", "-", + RowBox[{"v", " ", "\[Gamma]"}]}], ")"}]}]], "]"}]}], "]"}]}]}], + ";"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{"denomTwo", "[", + RowBox[{ + "v_", ",", "x_", ",", "y_", ",", "L_", ",", "S_", ",", "K_", ",", + "\[Sigma]_", ",", "\[Tau]_", ",", "\[Gamma]_"}], "]"}], ":=", " ", + RowBox[{"2", " ", "x"}]}], ";"}], "\n"}], "Code", + CellChangeTimes->{{3.9113877530057907`*^9, 3.911387759870617*^9}, { + 3.911387791900612*^9, 3.911387835019555*^9}, {3.911389040938622*^9, + 3.911389111156786*^9}, {3.911389254777446*^9, 3.9113892749054213`*^9}, { + 3.911389313552825*^9, 3.9113896479421587`*^9}, {3.911389684738289*^9, + 3.911389710567501*^9}, {3.911389764399426*^9, 3.911390120184866*^9}, { + 3.911390172200013*^9, 3.9113903097104073`*^9}, {3.9113903414495697`*^9, + 3.911390422476118*^9}, {3.9113904688729887`*^9, 3.911390469654806*^9}, { + 3.911390530287198*^9, 3.9113905408414717`*^9}, {3.9113907054945307`*^9, + 3.91139073520191*^9}, {3.91139090146146*^9, 3.911390906904234*^9}, { + 3.911390946256706*^9, 3.911391064802693*^9}, {3.911391222082168*^9, + 3.9113912438598137`*^9}, {3.9114061934461813`*^9, 3.911406197897942*^9}, { + 3.911409265595838*^9, 3.911409282641177*^9}, {3.9114093384435053`*^9, + 3.911409346040655*^9}, {3.911409904932541*^9, 3.911409910625359*^9}, + 3.911410010310408*^9, {3.9114111732761717`*^9, 3.911411210958044*^9}, { + 3.911411248268466*^9, 3.911411313752475*^9}, {3.911413086350491*^9, + 3.9114131282498713`*^9}, {3.91141319914225*^9, 3.911413480385025*^9}, { + 3.91675072235946*^9, 3.916750736241868*^9}, {3.916750835562142*^9, + 3.916750853604291*^9}, {3.916933983207768*^9, 3.916933985584363*^9}, { + 3.916934589170532*^9, 3.916934742188787*^9}, {3.91738501952264*^9, + 3.917385025286654*^9}, {3.918585613443736*^9, 3.918585616419739*^9}}, + CellLabel-> + "In[2450]:=",ExpressionUUID->"6bec84e7-f813-4bb5-9a1e-8a3d86e897da"], + +Cell[BoxData[ + GraphicsBox[{{{}, {}, + TagBox[ + {RGBColor[0.368417, 0.506779, 0.709798], AbsoluteThickness[1.6], Opacity[ + 1.], LineBox[CompressedData[" +1:eJwV0Ws01AkABXDklayUKWOijaOTOB5bK1ZxPUqiFCrPQliPEqMhMV7/VVGE +Etswq8Wiklc1ixAG46+EVDImJBKSsKi8Vh/uud/uh99VPhVg6yUiJCR0aCU/ +Onu0gtF6+Rej4nC9wE9X1Gr8RDbUllFkobFYnz4z0mw0Sw0rSaJsgZKozYz4 +oS6jPbmFapEUHaRJUHOTPgwb9Y00egZTjLE/PnzermfOqDThhQidcgQalprb +OG0SoPW+6ThLccMVdXHD4V55PBd3qfCnBOI5a4q33L0NlHeHHBProiEYvqkd +90of9B0Z78WG4qD0VJceG2SKzYyr420ByZDo8mK7u1jgO919uZCbit/9C/kC +njWKtstQM1r/BL+Jt/Wuuy3sRTvNFbTYMMps+PAw6xgy4uQ3Uqpvgyqtuk6m +zQFzYzrSqjXZeFFrc0yX54znbsd/Yo7mor1SKnt++SRSdgynCCbzMLxgIUoV +dkcgm0sXmyhA9IjhGcewU2hRKHQJkb8HXmioA0vOE7LTyy0TtPu4qM6yqU/x +Qsdb172lB4oxGincdkvbG10lFxOU/i2B/xZaTGiTD/Yop5R/4JTCeVDB+KSJ +H06wVEbtnpVBzsr1j3uC05ip8z2xr+0Btr+82gkXf1hN7XrNH36IgeXF/J75 +s9CauMM8Ov8InlXJDnqWgbjEeuFpu8DBVqk0jypJOjKtY2z/W18Oje01Okpd +dKiVDsXQ5CvA7HaMnUkPgmn/RK/qxkq46lgHDR09BwVqtEmn+mNccvBSmVRm +wHupcUhMswoD6744+Lxj4Nx4SpJgbzWyMtk2+SXBkL6zyjvdvgZTrTmDhswQ +0OQ0ol/bPwEzWWlJzuA8pk3PtAl21cJJtbJbVzIUqZ3sqOTyWjAPtOx2bg4F +0zLbnKNXB+0NO/Vfxl4At9FXtqiuDkplNbNuNmHYR5We9zath09/9W4x2XBs +DaB/nG6th3B/n+libziM2aWzJke4eBYY3K54m4ns0oZNIu1cGBR4fA04EQGP +FpZZqlMDPpUwBmrUI+FaaZXwsacBI5bfbC+MRqKy6EmXjE8jtDm3L9pVRsEl +Xtwgf7IRyx5H7lDMovHbbrVq4aAmXNKVsrPKjgZjUtpMa7EJdQGv3h5eHbPy +67MRxSgebgTxNVi+McifOpkfscRDRfOa/ZLNMeD6PfQuiWtG1GVrs7NaBGiV +gxb/rCbRL/NUZCmRwGKEKG1oDYkpJkuQl0Sg30R1TFWGxOoxX45zCoE80iMx +dz2JzTxJvwepBHZ2D3TkbCJxOsq8wzaDwMGv/U7ZmiTGJuqyHhcQiNj19kyW +DYn59vI9XlwCbt8XDPvsVvYQt/FjA4G9NYprfz5OQvK+/RdOEwEpc5fSv5xI +aMbP5ki2EEg71jPD9iARYvLrmsJ2AkWM7sjMYBJCZcXd4r0EUvS/HRacJ0HZ +EvXg7z4CjAWqsmIYCZVr1omn3hEwiHXgZkSS2O83blw+SIB3o0si4zIJQkWj +wHmMwF37uTf8eBJJyd9jRMcJXNskf5eWQIK9RDpXfCZwNOf4QVYyicd8n7Vz +kwT0vEOU+NdJvLbQHymeXvHTSPuscJPEe44E129mxe/zoyeO6SQWVbsyV82t ++JW9Sr51i8TS9byQqq8E/gd1j1xk + "]]}, + Annotation[#, "Charting`Private`Tag$18998#1"]& ]}, {}}, + AspectRatio->NCache[GoldenRatio^(-1), 0.6180339887498948], + Axes->{True, True}, + AxesLabel->{None, None}, + AxesOrigin->{0, 0}, + DisplayFunction->Identity, + Frame->{{False, False}, {False, False}}, + FrameLabel->{{None, None}, {None, None}}, + FrameTicks->{{Automatic, Automatic}, {Automatic, Automatic}}, + GridLines->{None, None}, + GridLinesStyle->Directive[ + GrayLevel[0.5, 0.4]], + ImagePadding->All, + Method->{ + "DefaultBoundaryStyle" -> Automatic, + "DefaultGraphicsInteraction" -> { + "Version" -> 1.2, "TrackMousePosition" -> {True, False}, + "Effects" -> { + "Highlight" -> {"ratio" -> 2}, "HighlightPoint" -> {"ratio" -> 2}, + "Droplines" -> { + "freeformCursorMode" -> True, + "placement" -> {"x" -> "All", "y" -> "None"}}}}, "DefaultMeshStyle" -> + AbsolutePointSize[6], "ScalingFunctions" -> None, + "CoordinatesToolOptions" -> {"DisplayFunction" -> ({ + (Identity[#]& )[ + Part[#, 1]], + (Identity[#]& )[ + Part[#, 2]]}& ), "CopiedValueFunction" -> ({ + (Identity[#]& )[ + Part[#, 1]], + (Identity[#]& )[ + Part[#, 2]]}& )}}, + PlotRange->{{0, 0.2}, {-2.8571428571428568`*^-9, 0.009263489123279008}}, + PlotRangeClipping->True, + PlotRangePadding->{{ + Scaled[0.02], + Scaled[0.02]}, { + Scaled[0.05], + Scaled[0.05]}}, + Ticks->{Automatic, Automatic}]], "Output", + CellChangeTimes->{ + 3.917383563255979*^9, 3.917384872931774*^9, {3.9173849757089787`*^9, + 3.9173850261946993`*^9}, 3.917385114645734*^9, 3.918585617114811*^9}, + CellLabel-> + "Out[2452]=",ExpressionUUID->"ff2c3a54-a3b1-4be6-afbb-32094a16572f"], + +Cell[CellGroupData[{ + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The optimal amount of X to tender is: \\!\\(\\*SubscriptBox[\\(\ +\[CapitalDelta]\\), \\(X\\)]\\) = \"\>", + "EchoLabel"], " ", "6.230446865679352`*^7"}]], "Echo", + CellChangeTimes->{ + 3.918585617388137*^9},ExpressionUUID->"6c3d9d24-049c-4137-b7e3-\ +6cf83d94b59c"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The amount out is: \\!\\(\\*SubscriptBox[\\(\[CapitalDelta]\\), \ +\\(Y\\)]\\) = \"\>", + "EchoLabel"], " ", + RowBox[{"-", "4.51112226444927`*^7"}]}]], "Echo", + CellChangeTimes->{ + 3.918585617396303*^9},ExpressionUUID->"255c6473-c036-4f07-8655-\ +af783da13368"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The resulting reserves are: (\\!\\(\\*SubscriptBox[\\(x\\), \ +\\(1\\)]\\),\\!\\(\\*SubscriptBox[\\(y\\), \\(1\\)]\\)) = \"\>", + "EchoLabel"], " ", + RowBox[{"{", + RowBox[{"1.0623044686567935`*^9", ",", "7.409463578271984`*^7"}], + "}"}]}]], "Echo", + CellChangeTimes->{ + 3.9185856174259367`*^9},ExpressionUUID->"a6e336c8-67e3-4548-aec6-\ +53634d7b4e40"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The final price of the pool is: P = \"\>", + "EchoLabel"], " ", "0.7033820512261256`"}]], "Echo", + CellChangeTimes->{ + 3.918585617433468*^9},ExpressionUUID->"5f8a3663-d1c0-4227-93e2-\ +6e4110936aef"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The equation to do root finding with is: \"\>", + "EchoLabel"], " ", + TemplateBox[{ + RowBox[{ + RowBox[{"-", "S"}], "+", + FractionBox[ + RowBox[{ + SuperscriptBox["\[ExponentialE]", + RowBox[{ + RowBox[{"-", + FractionBox["1", "2"]}], " ", "\[Sigma]", " ", + RowBox[{"(", + RowBox[{"\[Sigma]", "-", + RowBox[{"2", " ", + SqrtBox["2"], " ", + RowBox[{"InverseErfc", "[", + FractionBox[ + RowBox[{"2", " ", + RowBox[{"(", + RowBox[{"v", "+", "x"}], ")"}]}], + RowBox[{"L", "+", "v", "-", + RowBox[{"v", " ", "\[Gamma]"}]}]], "]"}]}]}], ")"}]}]], + " ", "K", " ", + RowBox[{"(", + RowBox[{"L", "+", + RowBox[{"x", " ", + RowBox[{"(", + RowBox[{ + RowBox[{"-", "1"}], "+", "\[Gamma]"}], ")"}]}]}], ")"}]}], + + RowBox[{"L", "+", "v", "-", + RowBox[{"v", " ", "\[Gamma]"}]}]], "+", + RowBox[{ + FractionBox["1", "2"], " ", "K", " ", + RowBox[{"(", + RowBox[{ + RowBox[{"-", "1"}], "+", "\[Gamma]"}], ")"}], " ", + RowBox[{"Erfc", "[", + RowBox[{ + FractionBox["\[Sigma]", + SqrtBox["2"]], "-", + RowBox[{"InverseErfc", "[", + FractionBox[ + RowBox[{"2", " ", + RowBox[{"(", + RowBox[{"v", "+", "x"}], ")"}]}], + RowBox[{"L", "+", "v", "-", + RowBox[{"v", " ", "\[Gamma]"}]}]], "]"}]}], "]"}]}]}], + RowBox[{"0", "\[LessEqual]", + FractionBox[ + RowBox[{"v", "+", "x"}], + RowBox[{"L", "+", "v", "-", + RowBox[{"v", " ", "\[Gamma]"}]}]], "\[LessEqual]", "1"}]}, + "ConditionalExpression"]}]], "Echo", + CellChangeTimes->{ + 3.918585617718585*^9},ExpressionUUID->"8b3d5769-45e4-45af-b1b7-\ +284acb1d88ab"] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["Raise External Price:", "Subsection", + CellChangeTimes->{{3.9114058960816593`*^9, + 3.911405902149222*^9}},ExpressionUUID->"d44a8cc6-b4f0-4204-b250-\ +e68ad4e19d99"], + +Cell[CellGroupData[{ + +Cell[TextData[{ + "Let ", + Cell[BoxData[ + FormBox[ + SubscriptBox["O", "Y"], TraditionalForm]],ExpressionUUID-> + "daf8aba3-0522-4fdc-b0f6-095c37f44dc7"], + " be the optimal amount of Y token to tender to get max arbitrage profit." +}], "Subsubsection", + CellChangeTimes->{{3.9114059041043043`*^9, + 3.9114059228094807`*^9}},ExpressionUUID->"800e047e-b69c-4509-bda4-\ +30799fa9a63e"], + +Cell[CellGroupData[{ + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + SubscriptBox["S", "ext"], " ", "=", " ", "2.2"}], ";", " ", + RowBox[{"Assert", "[", + RowBox[{ + SubscriptBox["S", "ext"], " ", ">", " ", + SubscriptBox["S", "0"]}], "]"}], ";"}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["Prof", "Raise"], "[", "in_", "]"}], " ", ":=", " ", + RowBox[{ + SubscriptBox["V", "A"], "[", + RowBox[{ + RowBox[{ + SubscriptBox["P", "M"], "[", + RowBox[{ + RowBox[{ + SubscriptBox["\[CapitalDelta]", "X"], "[", + RowBox[{"in", ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["K", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Tau]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}], ",", "in"}], "]"}], ",", " ", + + SubscriptBox["S", "ext"], ",", " ", + RowBox[{ + SubscriptBox["\[CapitalDelta]", "X"], "[", + RowBox[{"in", ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["K", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Tau]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}], "]"}]}], "\n", + RowBox[{"Plot", "[", + RowBox[{ + RowBox[{ + SubscriptBox["Prof", "Raise"], "[", "v", "]"}], ",", " ", + RowBox[{"{", + RowBox[{"v", ",", "0", ",", "0.2"}], "}"}]}], "]"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{ + SubscriptBox["O", "Y"], " ", "=", " ", + RowBox[{"ArgMax", "[", + RowBox[{ + RowBox[{"{", + RowBox[{ + RowBox[{ + SubscriptBox["Prof", "Raise"], "[", "y", "]"}], ",", " ", + RowBox[{"0", "<=", "y", "<=", + RowBox[{ + RowBox[{ + SubscriptBox["K", "0"], + SubscriptBox["L", "0"]}], "-", + SubscriptBox["y", "0"]}]}]}], "}"}], ",", " ", "y"}], "]"}]}], + ";"}], " "}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + SubscriptBox["O", "Y"], ",", " ", + "\"\\""}], "]"}], ";"}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"N", "[", + RowBox[{ + RowBox[{ + SubscriptBox["\[CapitalDelta]", "X"], "[", + RowBox[{ + SubscriptBox["O", "Y"], ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["K", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Tau]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}], ",", "18"}], "]"}], ",", " ", + "\"\\""}], "]"}], ";"}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"{", + RowBox[{ + RowBox[{ + SubscriptBox["x", "0"], " ", "+", " ", + RowBox[{ + SubscriptBox["\[CapitalDelta]", "X"], "[", + RowBox[{ + SubscriptBox["O", "Y"], ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["K", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Tau]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}], ",", " ", + RowBox[{ + SubscriptBox["y", "0"], " ", "+", " ", + SubscriptBox["O", "Y"]}]}], "}"}], ",", " ", + "\"\\""}], "]"}], ";"}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["P", "F"], " ", "=", " ", + RowBox[{ + SubscriptBox["P", "Y"], "[", + RowBox[{ + RowBox[{ + SubscriptBox["y", "0"], " ", "+", " ", + SubscriptBox["O", "Y"]}], ",", " ", + RowBox[{ + SubscriptBox["L", "0"], " ", "+", " ", + RowBox[{ + FractionBox["1", + SubscriptBox["K", "0"]], + RowBox[{ + SubscriptBox["\[Delta]", "in"], "[", + RowBox[{ + SubscriptBox["O", "Y"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}]}], ",", " ", + SubscriptBox["K", "0"], ",", " ", + SubscriptBox["\[Sigma]", "0"], ",", " ", + SubscriptBox["\[Tau]", "0"]}], "]"}]}], ";", " ", + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"N", "[", + RowBox[{ + SubscriptBox["P", "F"], ",", " ", "18"}], "]"}], ",", " ", + "\"\\""}], "]"}], ";"}], "\n", + RowBox[{ + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"FullSimplify", "[", + RowBox[{"D", "[", + RowBox[{ + RowBox[{ + SubscriptBox["V", "A"], "[", + RowBox[{ + RowBox[{ + SubscriptBox["P", "M"], "[", + RowBox[{ + RowBox[{ + SubscriptBox["\[CapitalDelta]", "X"], "[", + RowBox[{ + "v", ",", "x", ",", "y", ",", "L", ",", "K", ",", "\[Sigma]", + ",", "\[Tau]", ",", "\[Gamma]"}], "]"}], ",", "v"}], "]"}], ",", + " ", "S", ",", " ", + RowBox[{ + SubscriptBox["\[CapitalDelta]", "X"], "[", + RowBox[{ + "v", ",", "x", ",", "y", ",", "L", ",", "K", ",", "\[Sigma]", ",", + "\[Tau]", ",", "\[Gamma]"}], "]"}]}], "]"}], ",", "v"}], "]"}], + "]"}], ",", " ", "\"\\""}], + "]"}], ";"}], "\n", + RowBox[{"(*", " ", + RowBox[{ + "Check", " ", "that", " ", "the", " ", "trading", " ", "function", " ", + "is", " ", "invariant", " ", "under", " ", "the", " ", "swap"}], " ", + "*)"}]}], "\n", + RowBox[{"Assert", "[", + RowBox[{ + RowBox[{"Abs", "[", + RowBox[{"\[CurlyPhi]", "[", + RowBox[{ + RowBox[{ + SubscriptBox["x", "0"], " ", "+", " ", + RowBox[{ + SubscriptBox["\[CapitalDelta]", "X"], "[", + RowBox[{ + SubscriptBox["O", "Y"], ",", + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", + SubscriptBox["L", "0"], ",", + SubscriptBox["K", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Tau]", "0"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}], ",", " ", + RowBox[{ + SubscriptBox["y", "0"], " ", "+", " ", + SubscriptBox["O", "Y"]}], ",", " ", + RowBox[{ + SubscriptBox["L", "0"], " ", "+", " ", + RowBox[{ + FractionBox["1", + SubscriptBox["K", "0"]], + RowBox[{ + SubscriptBox["\[Delta]", "in"], "[", + RowBox[{ + SubscriptBox["O", "Y"], ",", + SubscriptBox["\[Gamma]", "0"]}], "]"}]}]}], ",", " ", + SubscriptBox["K", "0"], ",", + SubscriptBox["\[Sigma]", "0"], ",", + SubscriptBox["\[Tau]", "0"]}], "]"}], "]"}], " ", "<", " ", + SuperscriptBox["10", + RowBox[{"-", "14"}]]}], "]"}]}], "Code", + CellChangeTimes->CompressedData[" +1:eJwdxU0oQwEAB/D3nsumRlKGcVhtIsVFESPPmNmBfFxmiqxZjWFoDcUFS5NW +aj7bYUQ+pmjJVsJuyhSJ5OsiankjbT5Ww/u/w6+fuL2nQUcRBJHBwr1iQZ5B +wtA+5bQCS3yaOnznza3H/kBRC247W+B+Jt19eCRqHOBeGq/oZNe8CuSYP1da +jQvMWiWmnVVNODz43Yq3RZdarJ+eMODQvH0K1+g8DuyaTL3FjvDMPfY+lr/g +DtnaF1YcrsRw9/AH0cWefUVc4LHfo2scv296NLLv2mxPuKycTNdKGdoel8Yd +pJngYh5Dq+SWN2xeLiGX2BWhUQqH1L+mCDujPO/DTuGbFYcbrVP4L0DNYnKH +z12Zs+HEN7xt7hRV4ip2FWdy77nM5Ce79NRN4droQQoeSxIJMWHTF+KwLVmG +Le/rm+f5DM2LqLew/6ffg5mEB27LUPMxDsWyTvA//Rnfcg== + "], + CellLabel-> + "In[683]:=",ExpressionUUID->"7ca95c98-841f-4a4d-8646-7ee5087c0cbe"], + +Cell[BoxData[ + GraphicsBox[{{{}, {}, + TagBox[ + {RGBColor[0.368417, 0.506779, 0.709798], AbsoluteThickness[1.6], Opacity[ + 1.], LineBox[CompressedData[" +1:eJwV0nk41AkAxnH3VjOsh5GZIWrXdklbrVLE60oyZUWRUmzaBmUZDZWwM+Ws +IeNnKdKhlhw7a2yE1tWMaX9K0uEYR5aQI0fpcmTtH+/z/f/zvMsOB7n+rKSg +oLBrfv83e6ic2xC33sogIXXggPO0ZYCSTk0xTRMZ1N48O2qb1Qd6eNFF2lJQ +W6QG6tRBq623CldG0dZBJ0GJO0H5bPVysO5IKM0aomlzs17KQogFT5U4NBdI +FY02tlMYYHa1Nv1C84H58JegF5RVeKzmVR5IC8YH70KVRsoW0P7d5ZlYy8OB ++GHbQIYTOBsye1X74lFwjTHG8nWDAffCm8agZHhNDAcsyPDEFOenuUJJKkyD +uTmksQ9EqzTomQ2XMEVX7fzk4gsPlWcOjLVZcO2Ma0gqP4rMeN3FtMrr8Az7 +dsVmrQB8HF5HNarKhnJueytrfSAe+7irRwzdwrT3XHPPjmAINwwIOyZyMNKk +pb23i4PgLAlHdew2DEWmzk0BJ1DPKPQK0y2A00PZbgVGKDTfzdWPMf/AaAN9 +9Eh+GJo6ve3FO/7Eq5OhiyMOnkJLUYxgyd0i+Ff3Lc9aEI6ty4Rl/aVinDJk +MCbTzuBgxjdDbo+KobWCvXPWIRLva/0Pbmv8C+PaN2K3yqPAerupWT5wBwWB +BT1p23lYO5YXsWe6BBuXWkXTDPiIzXh6xHWmFC3Gs7tM+vm44sx3ndQqQ7WR +9aRB9VmsFPfxmbrleJ8X+KIg4Rxsu8e6jBZXIDHdosvSJRoMOs/m2ep7UKbW +Xr29IgbsL3V9qiZ/wzdnpFI0HoMTb4QXO+wr8XwqNyyxPhbUPGV2ukcVRHe5 +LM71ODC1jXnNHtUgFUJujrPj8c72eGPHphpsNvMLcrROQOqzrF+Ty2ow8RlN +FovOI8Ip26HUrBYO+45zTZ+ch6TOX1NUWwvdlpGjqrkXsI1OnWbb3odezxut +kCABvgvivH7XcB9V6ozxUsdEWGeJP9i4SLBwTfjldO0kZIulekpPJNBkICVS +ngTf+gy71P1SFC0/mm5RfBHeFSzB63YprsnsZPWWyagQVbdo+NXBr3Lgbm/+ +/K8S1MxzJ+qwvITDmtMRYovFykrFEBmuC5wHX58WgjtBtVs7K8NM4rFjVv1C +DEU9GtT/9QGGLdYoHmalIPftodzILw+gZhDLihelQBJwh10U/w88kmvYTCYB +ZsUrx98XkvA9fCYvPIrAbKQKs49CghdW027OI9BtYzRspEHiyiIFjWk+gRzS +N/GWFglThl/ImRgCP7T1NN3UI7FFedI8QkBg56fu/dkmJFqDJ+sjMwhEbuo8 +fm03iahifj+vlIDP1IzlSzcSV2UUuk0ZAfsq/a8N3UkUyM/vUKwgsMjBS3x1 +PwlH/5OF/EoCaXvb32f5ktjjphtyVkpAxG2LuhJKIlW+eubcUwLCzZ9/7DhJ +Qmevu4n9cwLcGfoy/XASpNLpQyrNBMyj90kyo0gIpYU10W0EHhAtX2XGkbgk +ehgd000g3+NjqzyBhHZeXcm2HgJJerr5TAEJSXNpv+orAntuuu/MSCZxg57o +GDtAwIwdtkSeQmK7LPC0w+C8n3HaKOO3eR91p3y14Xm/0ZJqz3QSrvcM22Uj +837FL5IvXybxvf5bStwogf8AM+pSBQ== + "]]}, + Annotation[#, "Charting`Private`Tag$100831#1"]& ]}, {}}, + AspectRatio->NCache[GoldenRatio^(-1), 0.6180339887498948], + Axes->{True, True}, + AxesLabel->{None, None}, + AxesOrigin->{0, 0}, + DisplayFunction->Identity, + Frame->{{False, False}, {False, False}}, + FrameLabel->{{None, None}, {None, None}}, + FrameTicks->{{Automatic, Automatic}, {Automatic, Automatic}}, + GridLines->{None, None}, + GridLinesStyle->Directive[ + GrayLevel[0.5, 0.4]], + ImagePadding->All, + Method->{ + "DefaultBoundaryStyle" -> Automatic, + "DefaultGraphicsInteraction" -> { + "Version" -> 1.2, "TrackMousePosition" -> {True, False}, + "Effects" -> { + "Highlight" -> {"ratio" -> 2}, "HighlightPoint" -> {"ratio" -> 2}, + "Droplines" -> { + "freeformCursorMode" -> True, + "placement" -> {"x" -> "All", "y" -> "None"}}}}, "DefaultMeshStyle" -> + AbsolutePointSize[6], "ScalingFunctions" -> None, + "CoordinatesToolOptions" -> {"DisplayFunction" -> ({ + (Identity[#]& )[ + Part[#, 1]], + (Identity[#]& )[ + Part[#, 2]]}& ), "CopiedValueFunction" -> ({ + (Identity[#]& )[ + Part[#, 1]], + (Identity[#]& )[ + Part[#, 2]]}& )}}, + PlotRange->{{0, 0.2}, {0., 0.01946459780131365}}, + PlotRangeClipping->True, + PlotRangePadding->{{ + Scaled[0.02], + Scaled[0.02]}, { + Scaled[0.05], + Scaled[0.05]}}, + Ticks->{Automatic, Automatic}]], "Output", + CellChangeTimes->{ + 3.9173835690887947`*^9, 3.917385059548512*^9, {3.9173851196395884`*^9, + 3.917385128134555*^9}, 3.9173852324926853`*^9, 3.917385290081284*^9}, + CellLabel-> + "Out[685]=",ExpressionUUID->"1f3d487e-7d60-4563-a2b3-f3aa65c1d146"], + +Cell[CellGroupData[{ + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The optimal amount of Y to tender is: \\!\\(\\*SubscriptBox[\\(\ +\[CapitalDelta]\\), \\(Y\\)]\\) = \"\>", + "EchoLabel"], " ", "364.4731544001954`"}]], "Echo", + CellChangeTimes->{ + 3.917385290172243*^9},ExpressionUUID->"20a8471e-d48d-4dc1-8a0f-\ +2b45a51698ee"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The amount out is: \\!\\(\\*SubscriptBox[\\(\[CapitalDelta]\\), \ +\\(X\\)]\\) = \"\>", + "EchoLabel"], " ", + RowBox[{"-", "173.56889322451593`"}]}]], "Echo", + CellChangeTimes->{ + 3.917385290180708*^9},ExpressionUUID->"5cd69ac1-3ee9-4f3c-b5cf-\ +95d07eb0ff1c"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The resulting reserves are: (\\!\\(\\*SubscriptBox[\\(x\\), \ +\\(1\\)]\\),\\!\\(\\*SubscriptBox[\\(y\\), \\(1\\)]\\)) = \"\>", + "EchoLabel"], " ", + RowBox[{"{", + RowBox[{"826.4311067754841`", ",", "2364.4731544001957`"}], + "}"}]}]], "Echo", + CellChangeTimes->{ + 3.917385290202286*^9},ExpressionUUID->"0834851c-ee77-4202-aa72-\ +ad7a0f04c869"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The final price of the pool is: P = \"\>", + "EchoLabel"], " ", "2.194468360784738`"}]], "Echo", + CellChangeTimes->{ + 3.917385290210438*^9},ExpressionUUID->"e88a31f0-9ce3-43a8-a4fe-\ +062f673969b4"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The equation to do root finding with is: \"\>", + "EchoLabel"], " ", + TemplateBox[{ + RowBox[{ + RowBox[{"-", "1"}], "+", + FractionBox[ + RowBox[{ + SuperscriptBox["\[ExponentialE]", + RowBox[{ + RowBox[{"-", + FractionBox[ + RowBox[{ + SuperscriptBox["\[Sigma]", "2"], " ", "\[Tau]"}], "2"]}], + "+", + RowBox[{ + SqrtBox["2"], " ", "\[Sigma]", " ", + SqrtBox["\[Tau]"], " ", + RowBox[{"InverseErfc", "[", + FractionBox[ + RowBox[{"2", " ", + RowBox[{"(", + RowBox[{"v", "+", "y"}], ")"}]}], + RowBox[{ + RowBox[{"K", " ", "L"}], "+", "v", "-", + RowBox[{"v", " ", "\[Gamma]"}]}]], "]"}]}]}]], " ", "S", + " ", + RowBox[{"(", + RowBox[{ + RowBox[{"K", " ", "L"}], "+", + RowBox[{"y", " ", + RowBox[{"(", + RowBox[{ + RowBox[{"-", "1"}], "+", "\[Gamma]"}], ")"}]}]}], ")"}]}], + + RowBox[{"K", " ", + RowBox[{"(", + RowBox[{ + RowBox[{"K", " ", "L"}], "+", "v", "-", + RowBox[{"v", " ", "\[Gamma]"}]}], ")"}]}]], "+", + FractionBox[ + RowBox[{"S", " ", + RowBox[{"(", + RowBox[{ + RowBox[{"-", "1"}], "+", "\[Gamma]"}], ")"}], " ", + RowBox[{"Erfc", "[", + RowBox[{ + FractionBox[ + RowBox[{"\[Sigma]", " ", + SqrtBox["\[Tau]"]}], + SqrtBox["2"]], "-", + RowBox[{"InverseErfc", "[", + FractionBox[ + RowBox[{"2", " ", + RowBox[{"(", + RowBox[{"v", "+", "y"}], ")"}]}], + RowBox[{ + RowBox[{"K", " ", "L"}], "+", "v", "-", + RowBox[{"v", " ", "\[Gamma]"}]}]], "]"}]}], "]"}]}], + RowBox[{"2", " ", "K"}]]}], + RowBox[{"0", "\[LessEqual]", + FractionBox[ + RowBox[{"v", "+", "y"}], + RowBox[{ + RowBox[{"K", " ", "L"}], "+", "v", "-", + RowBox[{"v", " ", "\[Gamma]"}]}]], "\[LessEqual]", "1"}]}, + "ConditionalExpression"]}]], "Echo", + CellChangeTimes->{ + 3.917385290654476*^9},ExpressionUUID->"24b12cbb-7a22-45fd-bb9e-\ +d5824114ff9b"] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]], + +Cell[CellGroupData[{ + +Cell["Parameter Updates", "Section", + CellChangeTimes->{{3.9114106614664173`*^9, + 3.911410663603334*^9}},ExpressionUUID->"b9760121-93f2-4105-bc65-\ +e6b3dbe04aec"], + +Cell[CellGroupData[{ + +Cell["\<\ +We want to let parameters change, then determine the new L from them.\ +\>", "Subsection", + CellChangeTimes->{{3.9114115124928417`*^9, + 3.911411520281617*^9}},ExpressionUUID->"bf3ecf5a-6ea2-4f9a-a236-\ +2d3956e9c134"], + +Cell[CellGroupData[{ + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + RowBox[{"{", + RowBox[{ + SubscriptBox["K", "1"], ",", + SubscriptBox["\[Sigma]", "1"], ",", + SubscriptBox["\[Tau]", "1"]}], "}"}], " ", "=", " ", + RowBox[{"{", + RowBox[{ + RowBox[{ + SubscriptBox["K", "0"], " ", "+", " ", + FractionBox["1", "10"]}], ",", " ", + RowBox[{ + SubscriptBox["\[Sigma]", "0"], " ", "-", " ", + FractionBox["1", "20"]}], ",", " ", + SubscriptBox["\[Tau]", "0"]}], "}"}]}], ";"}], "\n", + RowBox[{ + RowBox[{ + SubscriptBox["L", "1"], " ", "=", " ", + RowBox[{"L", " ", "/.", " ", + RowBox[{"FindRoot", "[", + RowBox[{ + RowBox[{"\[CurlyPhi]", "[", + RowBox[{ + SubscriptBox["x", "0"], ",", + SubscriptBox["y", "0"], ",", "L", ",", + SubscriptBox["K", "1"], ",", + SubscriptBox["\[Sigma]", "1"], ",", + SubscriptBox["\[Tau]", "1"]}], "]"}], ",", " ", + RowBox[{"{", + RowBox[{"L", ",", + SubscriptBox["L", "0"]}], "}"}]}], "]"}]}]}], ";"}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"N", "[", + RowBox[{ + SubscriptBox["L", "0"], ",", "18"}], "]"}], ",", " ", + "\"\\""}], "]"}], ";"}], "\n", + RowBox[{ + RowBox[{"Echo", "[", + RowBox[{ + RowBox[{"N", "[", + RowBox[{ + SubscriptBox["L", "1"], ",", "18"}], "]"}], ",", " ", + "\"\\""}], "]"}], ";"}]}], "Code", + CellChangeTimes->{{3.911411526467024*^9, 3.911411558889496*^9}, { + 3.911411603270329*^9, 3.911411726104031*^9}, {3.911411854447959*^9, + 3.911411871785728*^9}, {3.911412038711419*^9, 3.9114121340202703`*^9}, { + 3.911412179397016*^9, 3.91141220168312*^9}, {3.911412234164196*^9, + 3.9114122397709723`*^9}, {3.911412325036952*^9, 3.9114123356623907`*^9}, { + 3.91141236997156*^9, 3.911412427698132*^9}, {3.911412458150606*^9, + 3.911412466280922*^9}, {3.9114126475927057`*^9, 3.9114126927861547`*^9}}, + CellLabel-> + "In[477]:=",ExpressionUUID->"88153533-d3ba-42e3-8671-5c87d6eba466"], + +Cell[CellGroupData[{ + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The original liquidity was: \\!\\(\\*SubscriptBox[\\(L\\), \\(0\ +\\)]\\) = \"\>", + "EchoLabel"], " ", + "1.07205816303780375296378578465914117862`18."}]], "Echo", + CellChangeTimes->{ + 3.911412693179192*^9},ExpressionUUID->"9c86a598-6ad7-4cdc-b7d3-\ +f17754fbb4f8"], + +Cell[BoxData[ + RowBox[{ + TagBox["\<\"The new liquidity after parameter changes is: \ +\\!\\(\\*SubscriptBox[\\(L\\), \\(1\\)]\\) = \"\>", + "EchoLabel"], " ", "1.0633573081332175`"}]], "Echo", + CellChangeTimes->{ + 3.9114126932060432`*^9},ExpressionUUID->"edb1d49d-7246-436e-85d1-\ +f3028622cb8d"] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]] +}, +WindowSize->{2125, 2083}, +WindowMargins->{{Automatic, 1157}, {Automatic, -819}}, +PrintingCopies->1, +PrintingPageRange->{1, Automatic}, +FrontEndVersion->"13.2 for Mac OS X ARM (64-bit) (January 31, 2023)", +StyleDefinitions->FrontEnd`FileName[{$RootDirectory, "Users", "colin", + "Documents"}, "DarkMode.nb", CharacterEncoding -> "UTF-8"], +ExpressionUUID->"13daad44-0d0d-4117-a120-aa865447a233" +] +(* End of Notebook Content *) + +(* Internal cache information *) +(*CellTagsOutline +CellTagsIndex->{} +*) +(*CellTagsIndex +CellTagsIndex->{} +*) +(*NotebookFileOutline +Notebook[{ +Cell[CellGroupData[{ +Cell[422, 15, 185, 3, 194, "Title",ExpressionUUID->"2003d08a-fff7-4f74-8623-7a0823c9cafa"], +Cell[CellGroupData[{ +Cell[632, 22, 221, 5, 134, "Section",ExpressionUUID->"514be430-48c5-4dc6-92af-6b1c3a5b8586"], +Cell[CellGroupData[{ +Cell[878, 31, 222, 5, 107, "Subsection",ExpressionUUID->"f16f1652-ed41-4414-8c9b-6b6d5d8061ac"], +Cell[1103, 38, 579, 14, 69, "Code",ExpressionUUID->"8255c47c-fa0b-4fdd-8638-752453aca613"] +}, Open ]], +Cell[CellGroupData[{ +Cell[1719, 57, 202, 3, 107, "Subsection",ExpressionUUID->"b3dd161e-0b53-4183-b30c-be7844f26477"], +Cell[1924, 62, 809, 19, 111, "Code",ExpressionUUID->"25d6c1c4-f902-41e0-8746-c63f6b03d94e"] +}, Open ]], +Cell[CellGroupData[{ +Cell[2770, 86, 307, 7, 107, "Subsection",ExpressionUUID->"f601d02f-f91e-4780-a166-a78097a54f48"], +Cell[3080, 95, 978, 27, 205, "Code",ExpressionUUID->"d5d16a82-3e60-44ff-affc-b50eb9144304"] +}, Open ]], +Cell[CellGroupData[{ +Cell[4095, 127, 296, 7, 107, "Subsection",ExpressionUUID->"009a24ad-ebe5-4d73-bdda-a7839592332a"], +Cell[CellGroupData[{ +Cell[4416, 138, 244, 6, 89, "Subsubsection",ExpressionUUID->"3b15f5e3-f420-4095-899a-506c7286cc40"], +Cell[4663, 146, 2208, 59, 244, "Code",ExpressionUUID->"3950a1e9-9c32-45c4-b5ef-404c37d6ef65"] +}, Open ]], +Cell[CellGroupData[{ +Cell[6908, 210, 253, 6, 89, "Subsubsection",ExpressionUUID->"6228385e-cfd2-4bd0-97f4-58c98a5a994e"], +Cell[7164, 218, 1228, 36, 159, "Code",ExpressionUUID->"cdbca2c9-2426-4adf-8516-6c22e3b352b1"] +}, Open ]], +Cell[CellGroupData[{ +Cell[8429, 259, 186, 3, 89, "Subsubsection",ExpressionUUID->"18015876-38da-4fa9-82b7-a417d745ef90"], +Cell[8618, 264, 649, 16, 90, "Code",ExpressionUUID->"a65cb5ec-cdac-40e0-bb49-f3d8157592d9"] +}, Open ]] +}, Open ]] +}, Open ]], +Cell[CellGroupData[{ +Cell[9328, 287, 285, 7, 134, "Section",ExpressionUUID->"da815218-0c74-4720-a5c9-76f45781c5e2"], +Cell[CellGroupData[{ +Cell[9638, 298, 305, 7, 107, "Subsection",ExpressionUUID->"d461a415-44ca-4804-8248-6137a0f9449f"], +Cell[CellGroupData[{ +Cell[9968, 309, 2677, 52, 134, "Code",ExpressionUUID->"8d262a91-37a1-41fb-b1b3-ae8191b1ccda"], +Cell[CellGroupData[{ +Cell[12670, 365, 232, 6, 50, "Echo",ExpressionUUID->"63fa2685-ead2-42c3-8894-aa3d0f759278"], +Cell[12905, 373, 258, 7, 68, "Echo",ExpressionUUID->"ab767d72-08f9-45a3-8d7a-62a93aac3a75"], +Cell[13166, 382, 242, 6, 50, "Echo",ExpressionUUID->"865ae55a-ba52-403c-9134-dd94b3a70090"] +}, Open ]] +}, Open ]] +}, Open ]], +Cell[CellGroupData[{ +Cell[13469, 395, 307, 7, 107, "Subsection",ExpressionUUID->"a6874bc0-590c-4cb3-9d20-f010a014a154"], +Cell[CellGroupData[{ +Cell[13801, 406, 2471, 46, 111, "Code",ExpressionUUID->"085ca656-cc94-43cf-a8c6-c116e23e4910"], +Cell[CellGroupData[{ +Cell[16297, 456, 275, 7, 50, "Echo",ExpressionUUID->"d46f0b65-6c97-4fd7-a377-fd814b863b4a"], +Cell[16575, 465, 234, 6, 50, "Echo",ExpressionUUID->"d830d4bf-eeeb-4bb6-a004-32188869cd46"] +}, Open ]] +}, Open ]], +Cell[CellGroupData[{ +Cell[16858, 477, 251, 6, 89, "Subsubsection",ExpressionUUID->"d0f44536-b00f-4974-8c75-dd65dd319645"], +Cell[CellGroupData[{ +Cell[17134, 487, 1744, 46, 111, "Code",ExpressionUUID->"57112165-db6f-447c-9713-f9c85c6d4828"], +Cell[CellGroupData[{ +Cell[18903, 537, 279, 7, 50, "Echo",ExpressionUUID->"d45d2202-bcd3-4d72-9040-061ec9d3b3fd"], +Cell[19185, 546, 287, 7, 50, "Echo",ExpressionUUID->"7f4698d3-1c35-46e4-b14b-549da3ca9c05"] +}, Open ]] +}, Open ]] +}, Open ]], +Cell[CellGroupData[{ +Cell[19533, 560, 224, 4, 89, "Subsubsection",ExpressionUUID->"0dabba71-0908-48dd-b11c-9fc06ac2d79b"], +Cell[CellGroupData[{ +Cell[19782, 568, 1307, 35, 111, "Code",ExpressionUUID->"05df5500-1e66-49a9-afd7-256365f8b6dc"], +Cell[21092, 605, 217, 6, 50, "Echo",ExpressionUUID->"2780423d-6264-4b5d-a5a5-68bad0b82ef8"] +}, Open ]] +}, Open ]], +Cell[CellGroupData[{ +Cell[21358, 617, 284, 6, 89, "Subsubsection",ExpressionUUID->"6c78f375-83c5-40c5-a26f-9dfafb83427b"], +Cell[21645, 625, 1409, 42, 128, "Code",ExpressionUUID->"05cdeb1a-e1b0-40a7-af51-51ab9b75cc2e"] +}, Open ]] +}, Open ]] +}, Open ]], +Cell[CellGroupData[{ +Cell[23115, 674, 155, 3, 134, "Section",ExpressionUUID->"e0558e89-2c12-471a-af64-7b6a1457eb4d"], +Cell[CellGroupData[{ +Cell[23295, 681, 290, 7, 107, "Subsection",ExpressionUUID->"2f7348b3-443c-4a22-943e-943b7962999e"], +Cell[23588, 690, 8190, 168, 362, "Code",ExpressionUUID->"0716117c-5381-46be-8b57-f2cefa727879"], +Cell[CellGroupData[{ +Cell[31803, 862, 4800, 115, 486, "Code",ExpressionUUID->"2687c350-1150-4951-bcf0-28364fa5bf73"], +Cell[CellGroupData[{ +Cell[36628, 981, 230, 7, 50, "Echo",ExpressionUUID->"27baf199-5392-4622-872b-c04b2522ee2d"], +Cell[36861, 990, 235, 6, 50, "Echo",ExpressionUUID->"eecd11fc-1f1d-4de9-9e4a-07d53c8e0fbb"], +Cell[37099, 998, 225, 6, 50, "Echo",ExpressionUUID->"292d880d-854e-49ac-80b7-b67aa90a72e5"], +Cell[37327, 1006, 240, 7, 50, "Echo",ExpressionUUID->"38d83234-0ac4-4733-b57c-347036548a09"] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]], +Cell[CellGroupData[{ +Cell[37640, 1021, 204, 4, 134, "Section",ExpressionUUID->"65d2b88d-22b2-481a-935c-71bdce9f21f1"], +Cell[37847, 1027, 568, 13, 107, "Text",ExpressionUUID->"f20cba79-bbb2-4d1b-9349-a349e8b0b7a4"], +Cell[CellGroupData[{ +Cell[38440, 1044, 630, 15, 89, "Subsubsection",ExpressionUUID->"d7131855-64d1-451f-98f3-152b4aa9f3a5"], +Cell[39073, 1061, 685, 20, 137, "Code",ExpressionUUID->"fadd5b7c-a968-401d-9e42-d6ca30b3a2a6"] +}, Open ]], +Cell[CellGroupData[{ +Cell[39795, 1086, 268, 4, 107, "Subsection",ExpressionUUID->"eed8b815-af5d-458f-8082-a956f0fb88b9"], +Cell[CellGroupData[{ +Cell[40088, 1094, 460, 12, 89, "Subsubsection",ExpressionUUID->"b20dcf41-6dfe-404c-8167-50b070efca5f"], +Cell[CellGroupData[{ +Cell[40573, 1110, 11768, 323, 907, "Code",ExpressionUUID->"6bec84e7-f813-4bb5-9a1e-8a3d86e897da"], +Cell[52344, 1435, 3570, 78, 471, "Output",ExpressionUUID->"ff2c3a54-a3b1-4be6-afbb-32094a16572f"], +Cell[CellGroupData[{ +Cell[55939, 1517, 304, 7, 50, "Echo",ExpressionUUID->"6c3d9d24-049c-4137-b7e3-6cf83d94b59c"], +Cell[56246, 1526, 302, 8, 50, "Echo",ExpressionUUID->"255c6473-c036-4f07-8655-af783da13368"], +Cell[56551, 1536, 399, 10, 52, "Echo",ExpressionUUID->"a6e336c8-67e3-4548-aec6-53634d7b4e40"], +Cell[56953, 1548, 239, 6, 50, "Echo",ExpressionUUID->"5f8a3663-d1c0-4227-93e2-6e4110936aef"], +Cell[57195, 1556, 2090, 58, 241, "Echo",ExpressionUUID->"8b3d5769-45e4-45af-b1b7-284acb1d88ab"] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]], +Cell[CellGroupData[{ +Cell[59358, 1622, 171, 3, 107, "Subsection",ExpressionUUID->"d44a8cc6-b4f0-4204-b250-e68ad4e19d99"], +Cell[CellGroupData[{ +Cell[59554, 1629, 382, 10, 89, "Subsubsection",ExpressionUUID->"800e047e-b69c-4509-bda4-30799fa9a63e"], +Cell[CellGroupData[{ +Cell[59961, 1643, 7526, 215, 540, "Code",ExpressionUUID->"7ca95c98-841f-4a4d-8646-7ee5087c0cbe"], +Cell[67490, 1860, 3553, 78, 450, "Output",ExpressionUUID->"1f3d487e-7d60-4563-a2b3-f3aa65c1d146"], +Cell[CellGroupData[{ +Cell[71068, 1942, 301, 7, 50, "Echo",ExpressionUUID->"20a8471e-d48d-4dc1-8a0f-2b45a51698ee"], +Cell[71372, 1951, 301, 8, 50, "Echo",ExpressionUUID->"5cd69ac1-3ee9-4f3c-b5cf-95d07eb0ff1c"], +Cell[71676, 1961, 391, 10, 50, "Echo",ExpressionUUID->"0834851c-ee77-4202-aa72-ad7a0f04c869"], +Cell[72070, 1973, 238, 6, 50, "Echo",ExpressionUUID->"e88a31f0-9ce3-43a8-a4fe-062f673969b4"], +Cell[72311, 1981, 2460, 70, 173, "Echo",ExpressionUUID->"24b12cbb-7a22-45fd-bb9e-d5824114ff9b"] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]], +Cell[CellGroupData[{ +Cell[74856, 2060, 164, 3, 134, "Section",ExpressionUUID->"b9760121-93f2-4105-bc65-e6b3dbe04aec"], +Cell[CellGroupData[{ +Cell[75045, 2067, 227, 5, 108, "Subsection",ExpressionUUID->"bf3ecf5a-6ea2-4f9a-a236-2d3956e9c134"], +Cell[CellGroupData[{ +Cell[75297, 2076, 2145, 57, 224, "Code",ExpressionUUID->"88153533-d3ba-42e3-8671-5c87d6eba466"], +Cell[CellGroupData[{ +Cell[77467, 2137, 306, 8, 50, "Echo",ExpressionUUID->"9c86a598-6ad7-4cdc-b7d3-f17754fbb4f8"], +Cell[77776, 2147, 298, 7, 50, "Echo",ExpressionUUID->"edb1d49d-7246-436e-85d1-f3028622cb8d"] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]] +}, Open ]] +} +] +*) + From 677cd563eff5784f91e8f32969ba4b7764be4b2c Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Fri, 19 Apr 2024 16:08:49 -0400 Subject: [PATCH 06/14] add SYCoveredCall --- src/DFMM.sol | 5 +- src/PairStrategy.sol | 5 +- src/SYCoveredCall/SYCoveredCall.sol | 97 ++++++++++++++++++----- src/SYCoveredCall/SYCoveredCallMath.sol | 16 ++-- src/SYCoveredCall/SYCoveredCallSolver.sol | 4 +- src/interfaces/IDFMM.sol | 1 - src/interfaces/IStrategy.sol | 18 ++++- 7 files changed, 107 insertions(+), 39 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index 9b17aa1a..bb7eb44f 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -81,8 +81,7 @@ contract DFMM is IDFMM { totalLiquidity: 0, liquidityToken: address(liquidityToken), feeCollector: params.feeCollector, - controllerFee: params.controllerFee, - lastSwapTimestamp: block.timestamp + controllerFee: params.controllerFee }); ( @@ -227,8 +226,6 @@ contract DFMM is IDFMM { ) external payable lock returns (address, address, uint256, uint256) { SwapState memory state; - _pools[poolId].lastSwapTimestamp = block.timestamp; - ( state.valid, state.invariant, diff --git a/src/PairStrategy.sol b/src/PairStrategy.sol index 70d0cec9..bb47eef0 100644 --- a/src/PairStrategy.sol +++ b/src/PairStrategy.sol @@ -134,10 +134,11 @@ abstract contract PairStrategy is IStrategy { uint256 tokenOutIndex, uint256 amountIn, uint256 amountOut, - uint256 deltaLiquidity + uint256 deltaLiquidity, + bytes memory params ) { - bytes memory params = getPoolParams(poolId); + params = getPoolParams(poolId); (tokenInIndex, tokenOutIndex, amountIn, amountOut) = abi.decode(data, (uint256, uint256, uint256, uint256)); diff --git a/src/SYCoveredCall/SYCoveredCall.sol b/src/SYCoveredCall/SYCoveredCall.sol index e44370d9..3b9b94f1 100644 --- a/src/SYCoveredCall/SYCoveredCall.sol +++ b/src/SYCoveredCall/SYCoveredCall.sol @@ -12,7 +12,8 @@ import { computeDeltaGivenDeltaLRoundDown, computeDeltaLXIn, computeDeltaLYIn, - computeTau + computeTau, + computeKGivenLastPrice } from "src/SYCoveredCall/SYCoveredCallMath.sol"; import { decodeFeeUpdate, @@ -34,10 +35,9 @@ struct InternalParams { uint256 mean; uint256 width; uint256 maturity; - uint256 swapFee; address controller; - + uint256 lastTimestamp; IStandardizedYield SY; IPPrincipalToken PT; IPYieldToken YT; @@ -49,12 +49,9 @@ struct SYCoveredCallParams { uint256 mean; uint256 width; uint256 maturity; - uint256 swapFee; address controller; - - uint256 timestamp; - + uint256 lastTimestamp; IStandardizedYield SY; IPPrincipalToken PT; IPYieldToken YT; @@ -75,6 +72,10 @@ error InvalidPair(); /// @dev Thrown when meanAnchor <= ONE. error InvalidMeanAnchor(); +error InvalidTimestamp(); + +error InvalidComputedK(); + /// @dev Thrown when the computedL passed to swap does not satisfy the invariant check error InvalidComputedLiquidity(int256 invariant); @@ -82,6 +83,7 @@ uint256 constant MIN_WIDTH = 1; uint256 constant MAX_WIDTH = uint256(type(int256).max); uint256 constant MIN_MEAN = 1; uint256 constant MAX_MEAN = uint256(type(int256).max); +uint256 constant T_EPSILON = 200; /** * @title SYCoveredCall Strategy for DFMM. @@ -90,6 +92,7 @@ uint256 constant MAX_MEAN = uint256(type(int256).max); contract SYCoveredCall is PairStrategy { using FixedPointMathLib for int256; /// @inheritdoc IStrategy + string public constant override name = "SYCoveredCall"; mapping(uint256 => InternalParams) public internalParams; @@ -118,22 +121,22 @@ contract SYCoveredCall is PairStrategy { (reserves, totalLiquidity, params) = abi.decode(data, (uint256[], uint256, SYCoveredCallParams)); - IStandardizedYield SY = IStandardizedYield(pool.tokens[1]); + IStandardizedYield SY = IStandardizedYield(pool.tokens[0]); IPPrincipalToken PT = IPPrincipalToken(pool.tokens[1]); - params.timestamp = block.timestamp; + params.lastTimestamp = block.timestamp; int256 tau = int256(computeTau(params)); if (PT.SY() != address(SY)) { - revert InvalidPair(); + revert InvalidPair(); } if (PT.expiry() <= block.timestamp) { - revert InvalidMaturity(); + revert InvalidMaturity(); } if (params.meanAnchor <= 1 ether) { - revert InvalidMeanAnchor(); + revert InvalidMeanAnchor(); } if (pool.reserves.length != 2 || reserves.length != 2) { @@ -146,7 +149,8 @@ contract SYCoveredCall is PairStrategy { internalParams[poolId].maturity = internalParams[poolId].PT.expiry(); internalParams[poolId].meanAnchor = params.meanAnchor; - internalParams[poolId].mean = uint256(int256(params.meanAnchor).powWad(tau)); + internalParams[poolId].mean = + uint256(int256(params.meanAnchor).powWad(tau)); internalParams[poolId].width = params.width; internalParams[poolId].swapFee = params.swapFee; internalParams[poolId].controller = params.controller; @@ -187,7 +191,7 @@ contract SYCoveredCall is PairStrategy { params.mean = internalParams[poolId].mean; params.swapFee = internalParams[poolId].swapFee; params.maturity = internalParams[poolId].maturity; - params.timestamp = IDFMM(dfmm).pools(poolId).lastSwapTimestamp; + params.lastTimestamp = internalParams[poolId].lastTimestamp; return abi.encode(params); } @@ -209,35 +213,84 @@ contract SYCoveredCall is PairStrategy { uint256 tokenOutIndex, uint256 amountIn, uint256 amountOut, - uint256 deltaLiquidity + uint256 deltaLiquidity, + bytes memory params ) { - bytes memory params = getPoolParams(poolId); + // fetch computedL and swapTimestamp from data + params = getPoolParams(poolId); + SYCoveredCallParams memory ccParams = + abi.decode(params, (SYCoveredCallParams)); + uint256 computedL; - (tokenInIndex, tokenOutIndex, amountIn, amountOut, computedL) = - abi.decode(data, (uint256, uint256, uint256, uint256, uint256)); + uint256 swapTimestamp; + ( + tokenInIndex, + tokenOutIndex, + amountIn, + amountOut, + computedL, + swapTimestamp + ) = abi.decode( + data, (uint256, uint256, uint256, uint256, uint256, uint256) + ); + + if ( + swapTimestamp < internalParams[poolId].lastTimestamp + || swapTimestamp < block.timestamp - T_EPSILON + || swapTimestamp > block.timestamp + T_EPSILON + ) { + revert InvalidTimestamp(); + } + + // if timestamp is valid, append it to the poolParams for validation check + ccParams.lastTimestamp = swapTimestamp; + + // compute new K + ccParams.mean = + computeKGivenLastPrice(pool.reserves[0], computedL, ccParams); int256 computedInvariant = - tradingFunction(pool.reserves, computedL, params); + tradingFunction(pool.reserves, computedL, abi.encode(params)); if (computedInvariant < 0 || computedInvariant > EPSILON) { revert InvalidComputedLiquidity(computedInvariant); } deltaLiquidity = _computeSwapDeltaLiquidity( - pool, params, tokenInIndex, tokenOutIndex, amountIn, amountOut + pool, + abi.encode(params), + tokenInIndex, + tokenOutIndex, + amountIn, + amountOut ); pool.reserves[tokenInIndex] += amountIn; pool.reserves[tokenOutIndex] -= amountOut; - invariant = - tradingFunction(pool.reserves, computedL + deltaLiquidity, params); + invariant = tradingFunction( + pool.reserves, computedL + deltaLiquidity, abi.encode(params) + ); + params = abi.encode(ccParams); valid = invariant >= 0; //valid = invariant >= 0 && invariant <= EPSILON; } + function postSwapHook( + address, + uint256 poolId, + Pool memory, + bytes memory params + ) external onlyDFMM { + SYCoveredCallParams memory ccParams = + abi.decode(params, (SYCoveredCallParams)); + + internalParams[poolId].lastTimestamp = ccParams.lastTimestamp; + internalParams[poolId].mean = ccParams.mean; + } + /// @inheritdoc IStrategy function tradingFunction( uint256[] memory reserves, diff --git a/src/SYCoveredCall/SYCoveredCallMath.sol b/src/SYCoveredCall/SYCoveredCallMath.sol index 9894d5e5..94440f9d 100644 --- a/src/SYCoveredCall/SYCoveredCallMath.sol +++ b/src/SYCoveredCall/SYCoveredCallMath.sol @@ -31,12 +31,13 @@ function computeTradingFunction( } function computeTau(SYCoveredCallParams memory params) pure returns (uint256) { - if (params.timestamp >= params.maturity) { + if (params.lastTimestamp >= params.maturity) { return 0; } else { - return ONE * (params.maturity - params.timestamp) / YEAR; + return ONE * (params.maturity - params.lastTimestamp) / YEAR; } } + function computeDeltaGivenDeltaLRoundUp( uint256 reserve, uint256 deltaLiquidity, @@ -214,7 +215,11 @@ function computePriceGivenX( } // K = P1(x) / exp[ni(x/L)√(L + (1/2)v²t)] -function computeKGivenLastPrice(uint256 rX, uint256 L, SYCoveredCallParams memory params) pure returns (uint256 K) { +function computeKGivenLastPrice( + uint256 rX, + uint256 L, + SYCoveredCallParams memory params +) pure returns (uint256 K) { uint256 price = computePriceGivenX(rX, L, params); uint256 tau = computeTau(params); @@ -225,12 +230,9 @@ function computeKGivenLastPrice(uint256 rX, uint256 L, SYCoveredCallParams memor b.wadMul(int256(computeSigmaSqrtTau(params.width, tau))) - int256(a) ).expWad(); - K = price.divWadDown(exp); - - + K = price.divWadDown(uint256(exp)); } - function computePriceGivenY( uint256 rY, uint256 L, diff --git a/src/SYCoveredCall/SYCoveredCallSolver.sol b/src/SYCoveredCall/SYCoveredCallSolver.sol index 65d1ac4d..b72a6a0d 100644 --- a/src/SYCoveredCall/SYCoveredCallSolver.sol +++ b/src/SYCoveredCall/SYCoveredCallSolver.sol @@ -72,7 +72,7 @@ contract SYCoveredCallSolver { uint256 timestamp ) public view returns (SYCoveredCallParams memory) { SYCoveredCallParams memory params = getPoolParams(poolId); - params.timestamp = timestamp; + params.lastTimestamp = timestamp; return params; } @@ -305,7 +305,7 @@ contract SYCoveredCallSolver { } uint256 poolId = poolId; - (bool valid,,,,,,) = IStrategy(strategy).validateSwap( + (bool valid,,,,,,,) = IStrategy(strategy).validateSwap( address(this), poolId, pool, swapData ); diff --git a/src/interfaces/IDFMM.sol b/src/interfaces/IDFMM.sol index c446ddc8..1c69a805 100644 --- a/src/interfaces/IDFMM.sol +++ b/src/interfaces/IDFMM.sol @@ -19,7 +19,6 @@ struct Pool { address liquidityToken; address feeCollector; uint256 controllerFee; - uint256 lastSwapTimestamp; } /** diff --git a/src/interfaces/IStrategy.sol b/src/interfaces/IStrategy.sol index 0b10cd9a..199ccdc1 100644 --- a/src/interfaces/IStrategy.sol +++ b/src/interfaces/IStrategy.sol @@ -124,6 +124,7 @@ interface IStrategy { * @return amountIn Amount of token sent by the swapper (in WAD). * @return amountOut Amount of token received by the swapper (in WAD). * @return deltaLiquidity Amount of liquidity generated by the swap fees. + * @return params Additional parameters for the strategy. */ function validateSwap( address sender, @@ -140,9 +141,24 @@ interface IStrategy { uint256 tokenOutIndex, uint256 amountIn, uint256 amountOut, - uint256 deltaLiquidity + uint256 deltaLiquidity, + bytes calldata params ); + /** + * @notice Validates a swap of tokens. + * @param sender Address that called the DFMM contract. + * @param poolId Id of the pool to swap tokens in. + * @param pool Structure containing the pool. + * @param data Additional data for the strategy. + */ + function postSwapHook( + address sender, + uint256 poolId, + Pool calldata pool, + bytes calldata data + ) external; + /** * @notice Updates the pool parameters. * @dev Strategies are free to implement the control mechanism From 6decfb1069205ddbf10ca4efb036c920a2104095 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Fri, 19 Apr 2024 16:26:26 -0400 Subject: [PATCH 07/14] add postSwapHooks --- src/CoveredCall/CoveredCall.sol | 26 +++++++++++++++++++---- src/DFMM.sol | 8 ++++++- src/NTokenStrategy.sol | 12 +++++++++-- src/PairStrategy.sol | 7 ++++++ src/SYCoveredCall/SYCoveredCall.sol | 2 +- src/SYCoveredCall/SYCoveredCallSolver.sol | 16 ++++++-------- 6 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/CoveredCall/CoveredCall.sol b/src/CoveredCall/CoveredCall.sol index 198708dc..3b20cefa 100644 --- a/src/CoveredCall/CoveredCall.sol +++ b/src/CoveredCall/CoveredCall.sol @@ -33,6 +33,7 @@ struct InternalParams { uint256 maturity; uint256 swapFee; address controller; + uint256 lastTimestamp; } /// @dev Parameterization of the Log Normal curve. @@ -42,7 +43,7 @@ struct CoveredCallParams { uint256 maturity; uint256 swapFee; address controller; - uint256 timestamp; + uint256 lastTimestamp; } /// @dev Thrown when the mean parameter is not within the allowed bounds. @@ -158,7 +159,7 @@ contract CoveredCall is PairStrategy { params.mean = internalParams[poolId].mean; params.swapFee = internalParams[poolId].swapFee; params.maturity = internalParams[poolId].maturity; - params.timestamp = IDFMM(dfmm).pools(poolId).lastSwapTimestamp; + params.lastTimestamp = internalParams[poolId].lastTimestamp; return abi.encode(params); } @@ -180,10 +181,18 @@ contract CoveredCall is PairStrategy { uint256 tokenOutIndex, uint256 amountIn, uint256 amountOut, - uint256 deltaLiquidity + uint256 deltaLiquidity, + bytes memory params ) { - bytes memory params = getPoolParams(poolId); + params = getPoolParams(poolId); + + CoveredCallParams memory ccParams = + abi.decode(params, (CoveredCallParams)); + ccParams.lastTimestamp = block.timestamp; + + params = abi.encode(ccParams); + uint256 computedL; (tokenInIndex, tokenOutIndex, amountIn, amountOut, computedL) = abi.decode(data, (uint256, uint256, uint256, uint256, uint256)); @@ -209,6 +218,15 @@ contract CoveredCall is PairStrategy { //valid = invariant >= 0 && invariant <= EPSILON; } + function postSwapHook( + address, + uint256 poolId, + Pool calldata pool, + bytes calldata + ) external override onlyDFMM { + internalParams[poolId].lastTimestamp = block.timestamp; + } + /// @inheritdoc IStrategy function tradingFunction( uint256[] memory reserves, diff --git a/src/DFMM.sol b/src/DFMM.sol index bb7eb44f..66ae70a2 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -225,6 +225,7 @@ contract DFMM is IDFMM { bytes calldata callbackData ) external payable lock returns (address, address, uint256, uint256) { SwapState memory state; + bytes memory postSwapHookData; ( state.valid, @@ -233,7 +234,8 @@ contract DFMM is IDFMM { state.tokenOutIndex, state.amountIn, state.amountOut, - state.deltaLiquidity + state.deltaLiquidity, + postSwapHookData ) = IStrategy(_pools[poolId].strategy).validateSwap( msg.sender, poolId, _pools[poolId], data ); @@ -264,6 +266,10 @@ contract DFMM is IDFMM { // Optimistically transfer the output tokens to the recipient. _transfer(state.tokenOut, recipient, state.amountOut); + IStrategy(_pools[poolId].strategy).postSwapHook( + msg.sender, poolId, _pools[poolId], postSwapHookData + ); + // If the callbackData is empty, do a regular `_transferFrom()` call, as in the other operations. if (callbackData.length == 0) { _transferFrom(tokens, amounts); diff --git a/src/NTokenStrategy.sol b/src/NTokenStrategy.sol index 281dab90..d8a86933 100644 --- a/src/NTokenStrategy.sol +++ b/src/NTokenStrategy.sol @@ -128,10 +128,11 @@ abstract contract NTokenStrategy is IStrategy { uint256 tokenOutIndex, uint256 amountIn, uint256 amountOut, - uint256 deltaLiquidity + uint256 deltaLiquidity, + bytes memory params ) { - bytes memory params = getPoolParams(poolId); + params = getPoolParams(poolId); (tokenInIndex, tokenOutIndex, amountIn, amountOut) = abi.decode(data, (uint256, uint256, uint256, uint256)); @@ -150,6 +151,13 @@ abstract contract NTokenStrategy is IStrategy { valid = invariant >= 0; } + function postSwapHook( + address, + uint256, + Pool memory, + bytes memory + ) external { } + /// @inheritdoc IStrategy function getPoolParams(uint256 poolId) public diff --git a/src/PairStrategy.sol b/src/PairStrategy.sol index bb47eef0..40ca25cf 100644 --- a/src/PairStrategy.sol +++ b/src/PairStrategy.sol @@ -157,6 +157,13 @@ abstract contract PairStrategy is IStrategy { valid = invariant >= 0; } + function postSwapHook( + address, + uint256, + Pool memory, + bytes calldata + ) external onlyDFMM { } + /// @inheritdoc IStrategy function getPoolParams(uint256 poolId) public diff --git a/src/SYCoveredCall/SYCoveredCall.sol b/src/SYCoveredCall/SYCoveredCall.sol index 3b9b94f1..ddfa4f5a 100644 --- a/src/SYCoveredCall/SYCoveredCall.sol +++ b/src/SYCoveredCall/SYCoveredCall.sol @@ -283,7 +283,7 @@ contract SYCoveredCall is PairStrategy { uint256 poolId, Pool memory, bytes memory params - ) external onlyDFMM { + ) external override onlyDFMM { SYCoveredCallParams memory ccParams = abi.decode(params, (SYCoveredCallParams)); diff --git a/src/SYCoveredCall/SYCoveredCallSolver.sol b/src/SYCoveredCall/SYCoveredCallSolver.sol index b72a6a0d..bee19b03 100644 --- a/src/SYCoveredCall/SYCoveredCallSolver.sol +++ b/src/SYCoveredCall/SYCoveredCallSolver.sol @@ -31,6 +31,7 @@ import { computeAllocationGivenDeltaY, computeDeallocationGivenDeltaX, computeDeallocationGivenDeltaY, + computeKGivenLastPrice, YEAR, ONE } from "src/SYCoveredCall/SYCoveredCallMath.sol"; @@ -220,22 +221,24 @@ contract SYCoveredCallSolver { function simulateSwap( uint256 poolId, bool swapXIn, - uint256 amountIn + uint256 amountIn, + uint256 timestamp ) public view returns (bool, uint256, uint256, bytes memory) { Reserves memory endReserves; (uint256[] memory preReserves, uint256 preTotalLiquidity) = getReservesAndLiquidity(poolId); SYCoveredCallParams memory poolParams = - getPoolParamsCustomTimestamp(poolId, block.timestamp); + getPoolParamsCustomTimestamp(poolId, timestamp); SimulateSwapState memory state; uint256 startComputedL = getNextLiquidity( poolId, preReserves[0], preReserves[1], preTotalLiquidity ); + poolParams.mean = computeKGivenLastPrice( + preReserves[0], preTotalLiquidity, poolParams + ); { - console2.log("startComputedL", startComputedL); - if (swapXIn) { state.deltaLiquidity = computeDeltaLXIn( amountIn, @@ -244,20 +247,15 @@ contract SYCoveredCallSolver { preTotalLiquidity, poolParams ); - console2.log("state.deltaLiquidity", state.deltaLiquidity); endReserves.rx = preReserves[0] + amountIn; endReserves.L = startComputedL + state.deltaLiquidity; - console2.log("endReserves.rx", endReserves.rx); - console2.log("endReserves.L", endReserves.L); uint256 approxPrice = getPriceGivenXL(poolId, endReserves.rx, endReserves.L); - console2.log("approxPrice", approxPrice); endReserves.ry = getNextReserveY( poolId, endReserves.rx, endReserves.L, approxPrice ); - console2.log("endReserves.ry", endReserves.ry); require( endReserves.ry < preReserves[1], From 96b29e9ae2e7305fbf533be49db0d09e68edc674 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Fri, 19 Apr 2024 16:49:45 -0400 Subject: [PATCH 08/14] SYCC test init modifier --- src/SYCoveredCall/SYCoveredCall.sol | 8 +- test/SYCoveredCall/unit/Allocate.t.sol | 141 ++++++++++++++++++++ test/SYCoveredCall/unit/Deallocate.t.sol | 71 ++++++++++ test/SYCoveredCall/unit/Init.t.sol | 8 ++ test/SYCoveredCall/unit/SetUp.sol | 131 +++++++++++++++++++ test/SYCoveredCall/unit/Swap.t.sol | 160 +++++++++++++++++++++++ 6 files changed, 513 insertions(+), 6 deletions(-) create mode 100644 test/SYCoveredCall/unit/Allocate.t.sol create mode 100644 test/SYCoveredCall/unit/Deallocate.t.sol create mode 100644 test/SYCoveredCall/unit/Init.t.sol create mode 100644 test/SYCoveredCall/unit/SetUp.sol create mode 100644 test/SYCoveredCall/unit/Swap.t.sol diff --git a/src/SYCoveredCall/SYCoveredCall.sol b/src/SYCoveredCall/SYCoveredCall.sol index ddfa4f5a..5ea2ea4f 100644 --- a/src/SYCoveredCall/SYCoveredCall.sol +++ b/src/SYCoveredCall/SYCoveredCall.sol @@ -31,7 +31,6 @@ enum UpdateCode { } struct InternalParams { - uint256 meanAnchor; uint256 mean; uint256 width; uint256 maturity; @@ -45,7 +44,6 @@ struct InternalParams { /// @dev Parameterization of the Log Normal curve. struct SYCoveredCallParams { - uint256 meanAnchor; uint256 mean; uint256 width; uint256 maturity; @@ -135,7 +133,7 @@ contract SYCoveredCall is PairStrategy { revert InvalidMaturity(); } - if (params.meanAnchor <= 1 ether) { + if (params.mean <= 1 ether) { revert InvalidMeanAnchor(); } @@ -148,9 +146,7 @@ contract SYCoveredCall is PairStrategy { internalParams[poolId].YT = IPYieldToken(PT.YT()); internalParams[poolId].maturity = internalParams[poolId].PT.expiry(); - internalParams[poolId].meanAnchor = params.meanAnchor; - internalParams[poolId].mean = - uint256(int256(params.meanAnchor).powWad(tau)); + internalParams[poolId].mean = params.mean; internalParams[poolId].width = params.width; internalParams[poolId].swapFee = params.swapFee; internalParams[poolId].controller = params.controller; diff --git a/test/SYCoveredCall/unit/Allocate.t.sol b/test/SYCoveredCall/unit/Allocate.t.sol new file mode 100644 index 00000000..78833b64 --- /dev/null +++ b/test/SYCoveredCall/unit/Allocate.t.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "./SetUp.sol"; +import { computeDeltaGivenDeltaLRoundUp } from + "src/SYCoveredCall/SYCoveredCallMath.sol"; +import { + computeDeltaLGivenDeltaY, + computeDeltaLGivenDeltaX, + computeDeltaXGivenDeltaL, + computeDeltaYGivenDeltaL +} from "src/lib/StrategyLib.sol"; + +contract SYCoveredCallAllocateTest is SYCoveredCallSetUp { + function test_SYCoveredCall_allocate_GivenL() public init { + (uint256[] memory reserves, uint256 totalLiquidity) = + solver.getReservesAndLiquidity(POOL_ID); + + uint256 deltaLiquidity = 0.1 ether; + uint256 maxDeltaX = computeDeltaGivenDeltaLRoundUp( + reserves[0], deltaLiquidity, totalLiquidity + ); + uint256 maxDeltaY = computeDeltaGivenDeltaLRoundUp( + reserves[1], deltaLiquidity, totalLiquidity + ); + + (, uint256 preTotalLiquidity) = solver.getReservesAndLiquidity(POOL_ID); + uint256 preLiquidityBalance = liquidityOf(address(this), POOL_ID); + console2.log(preTotalLiquidity); + console2.log(preLiquidityBalance); + + bytes memory data = abi.encode(maxDeltaX, maxDeltaY, deltaLiquidity); + dfmm.allocate(POOL_ID, data); + + (, uint256 postTotalLiquidity) = solver.getReservesAndLiquidity(POOL_ID); + uint256 postLiquidityBalance = liquidityOf(address(this), POOL_ID); + console2.log(postTotalLiquidity); + console2.log(postLiquidityBalance); + + uint256 deltaTotalLiquidity = postTotalLiquidity - preTotalLiquidity; + uint256 deltaLiquidityBalance = + postLiquidityBalance - preLiquidityBalance; + + assertEq(deltaTotalLiquidity, deltaLiquidityBalance); + } + + function test_SYCoveredCall_allocate_GivenX() public init { + uint256 deltaX = 0.1 ether; + + (uint256[] memory reserves, uint256 liquidity) = + solver.getReservesAndLiquidity(POOL_ID); + + uint256 deltaLiquidity = + computeDeltaLGivenDeltaX(deltaX, liquidity, reserves[0]); + uint256 deltaYMax = + computeDeltaYGivenDeltaL(deltaLiquidity, liquidity, reserves[1]); + // uint256 preLiquidityBalance = liquidityOf(address(this), POOL_ID); + // (,, uint256 preTotalLiquidity) = dfmm.getReservesAndLiquidity(POOL_ID); + + bytes memory data = abi.encode(deltaX, deltaYMax, deltaLiquidity); + dfmm.allocate(POOL_ID, data); + + /* + (,, uint256 postTotalLiquidity) = dfmm.getReservesAndLiquidity(POOL_ID); + uint256 deltaTotalLiquidity = postTotalLiquidity - preTotalLiquidity; + assertEq( + preLiquidityBalance + deltaTotalLiquidity, + liquidityOf(address(this), POOL_ID) + ); + */ + } + + // if we assert positive invariant, not less than epsilon, can someone sandwich a tx whereby the put the invariant to some extremely large number in front of an allocate? + + function test_SYCoveredCall_allocate_GivenY() public init { + uint256 maxDeltaY = 0.1 ether; + + (uint256[] memory reserves, uint256 liquidity) = + solver.getReservesAndLiquidity(POOL_ID); + + uint256 deltaLiquidity = + computeDeltaLGivenDeltaY(maxDeltaY, liquidity, reserves[1]); + uint256 maxDeltaX = + computeDeltaXGivenDeltaL(deltaLiquidity, liquidity, reserves[0]); + console2.log(maxDeltaX); + + // uint256 preLiquidityBalance = liquidityOf(address(this), POOL_ID); + // (,, uint256 preTotalLiquidity) = dfmm.getReservesAndLiquidity(POOL_ID); + + bytes memory data = abi.encode(maxDeltaX, maxDeltaY, deltaLiquidity); + dfmm.allocate(POOL_ID, data); + + /* + (,, uint256 postTotalLiquidity) = dfmm.getReservesAndLiquidity(POOL_ID); + uint256 deltaTotalLiquidity = postTotalLiquidity - preTotalLiquidity; + assertEq( + preLiquidityBalance + deltaTotalLiquidity, + liquidityOf(address(this), POOL_ID) + ); + */ + } + + function test_SYCoveredCall_allocate_x_maintains_price() public init { + uint256 startPrice = solver.internalPrice(POOL_ID); + uint256 deltaX = 0.77 ether; + + (uint256[] memory reserves, uint256 liquidity) = + solver.getReservesAndLiquidity(POOL_ID); + + uint256 deltaLiquidity = + computeDeltaLGivenDeltaX(deltaX, liquidity, reserves[0]); + uint256 deltaYMax = + computeDeltaYGivenDeltaL(deltaLiquidity, liquidity, reserves[1]); + + bytes memory data = abi.encode(deltaX, deltaYMax, deltaLiquidity); + dfmm.allocate(POOL_ID, data); + + uint256 endPrice = solver.internalPrice(POOL_ID); + + assertEq(startPrice, endPrice); + } + + function test_SYCoveredCall_allocate_y_maintains_price() public init { + uint256 maxDeltaY = 0.77 ether; + uint256 startPrice = solver.internalPrice(POOL_ID); + + (uint256[] memory reserves, uint256 liquidity) = + solver.getReservesAndLiquidity(POOL_ID); + + uint256 deltaLiquidity = + computeDeltaLGivenDeltaY(maxDeltaY, liquidity, reserves[1]); + uint256 maxDeltaX = + computeDeltaXGivenDeltaL(deltaLiquidity, liquidity, reserves[0]); + + bytes memory data = abi.encode(maxDeltaX, maxDeltaY, deltaLiquidity); + dfmm.allocate(POOL_ID, data); + uint256 endPrice = solver.internalPrice(POOL_ID); + + assertEq(startPrice, endPrice); + } +} diff --git a/test/SYCoveredCall/unit/Deallocate.t.sol b/test/SYCoveredCall/unit/Deallocate.t.sol new file mode 100644 index 00000000..bf20fd99 --- /dev/null +++ b/test/SYCoveredCall/unit/Deallocate.t.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "./SetUp.sol"; +import { + computeDeltaLGivenDeltaX, + computeDeltaYGivenDeltaX, + computeDeltaLGivenDeltaY, + computeDeltaXGivenDeltaL +} from "src/lib/StrategyLib.sol"; + +contract SYCoveredCallDeallocateTest is SYCoveredCallSetUp { + function test_SYCoveredCall_deallocate_GivenX() public init { + uint256 minDeltaX = 0.1 ether; + + (uint256[] memory reserves, uint256 liquidity) = + solver.getReservesAndLiquidity(POOL_ID); + uint256 deltaLiquidity = + computeDeltaLGivenDeltaX(minDeltaX, liquidity, reserves[0]); + uint256 minDeltaY = + computeDeltaYGivenDeltaX(minDeltaX, reserves[0], reserves[1]); + + // uint256 preLiquidityBalance = liquidityOf(address(this), POOL_ID); + // (,, uint256 preTotalLiquidity) = dfmm.getReservesAndLiquidity(POOL_ID); + + // TODO: See if we can get a better rounding because the transaction fails + // if we don't provide a small slippage toleralance. + bytes memory data = + abi.encode(minDeltaX - 10, minDeltaY - 10, deltaLiquidity); + dfmm.deallocate(POOL_ID, data); + + /* + (,, uint256 postTotalLiquidity) = dfmm.getReservesAndLiquidity(POOL_ID); + uint256 deltaTotalLiquidity = preTotalLiquidity - postTotalLiquidity; + assertEq( + preLiquidityBalance - deltaTotalLiquidity, + liquidityOf(address(this), POOL_ID) + ); + */ + } + + function test_SYCoveredCall_deallocate_GivenY() public init { + uint256 minDeltaY = 0.1 ether; + + (uint256[] memory reserves, uint256 liquidity) = + solver.getReservesAndLiquidity(POOL_ID); + + uint256 deltaLiquidity = + computeDeltaLGivenDeltaY(minDeltaY, liquidity, reserves[1]); + uint256 minDeltaX = + computeDeltaXGivenDeltaL(deltaLiquidity, liquidity, reserves[0]); + + // uint256 preLiquidityBalance = liquidityOf(address(this), POOL_ID); + // (,, uint256 preTotalLiquidity) = dfmm.getReservesAndLiquidity(POOL_ID); + + // TODO: See if we can get a better rounding because the transaction fails + // if we don't provide a small slippage toleralance. + bytes memory data = + abi.encode(minDeltaX - 10, minDeltaY - 10, deltaLiquidity); + dfmm.deallocate(POOL_ID, data); + + /* + (,, uint256 postTotalLiquidity) = dfmm.getReservesAndLiquidity(POOL_ID); + uint256 deltaTotalLiquidity = preTotalLiquidity - postTotalLiquidity; + assertEq( + preLiquidityBalance - deltaTotalLiquidity, + liquidityOf(address(this), POOL_ID) + ); + */ + } +} diff --git a/test/SYCoveredCall/unit/Init.t.sol b/test/SYCoveredCall/unit/Init.t.sol new file mode 100644 index 00000000..5fd8fa10 --- /dev/null +++ b/test/SYCoveredCall/unit/Init.t.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "./SetUp.sol"; +import "forge-std/Test.sol"; +import "forge-std/console2.sol"; + +contract SYCoveredCallInitTest is SYCoveredCallSetUp { } diff --git a/test/SYCoveredCall/unit/SetUp.sol b/test/SYCoveredCall/unit/SetUp.sol new file mode 100644 index 00000000..d7cfe4ae --- /dev/null +++ b/test/SYCoveredCall/unit/SetUp.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "src/SYCoveredCall/SYCoveredCall.sol"; +import "src/SYCoveredCall/SYCoveredCallSolver.sol"; +import "test/utils/SetUp.sol"; +import { ONE } from "src/lib/StrategyLib.sol"; +import { YEAR } from "src/SYCoveredCall/SYCoveredCallMath.sol"; +import { InitParams } from "src/interfaces/IDFMM.sol"; +import "forge-std/console2.sol"; +import { IPMarket } from "pendle/interfaces/IPMarket.sol"; +import "pendle/core/Market/MarketMathCore.sol"; +import "pendle/interfaces/IPAllActionV3.sol"; + +contract SYCoveredCallSetUp is SetUp { + using MarketMathCore for MarketState; + using MarketMathCore for int256; + using MarketMathCore for uint256; + + SYCoveredCall coveredCall; + SYCoveredCallSolver solver; + + uint256 public POOL_ID; + uint256 public constant FEE = 0.00001 ether; + + uint256 defaultReserveX = 100 ether; + uint256 defaultReserveXMil = 1_000_000 ether; + uint256 defaultReserveXDeep = ONE * 10_000_000; + + uint256 defaultPrice = ONE; + uint256 defaultPricePoint9Rate = 0.84167999326 ether; + + address public constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //real wsteth + IPMarket public constant market = + IPMarket(0xC374f7eC85F8C7DE3207a10bB1978bA104bdA3B2); + IPAllActionV3 public constant router = + IPAllActionV3(0x00000000005BBB0EF59571E58418F9a4357b68A0); + + IStandardizedYield public SY; + IPPrincipalToken public PT; + IPYieldToken public YT; + MarketState public pendleMarketState; + int256 pendleRateAnchor; + int256 pendleRateScalar; + uint256 timeToExpiry; + + function mintSY(uint256 amount) public { + IERC20(wstETH).approve(address(SY), type(uint256).max); + SY.deposit(address(this), address(wstETH), amount, 1); + } + + function mintPtYt(uint256 amount) public { + SY.transfer(address(YT), amount); + uint256 pyOut = YT.mintPY(address(this), address(this)); + uint256 ptBal = PT.balanceOf(address(this)); + uint256 ytBal = YT.balanceOf(address(this)); + uint256 syBal = SY.balanceOf(address(this)); + } + + function setUp() public override { + vm.createSelectFork({ urlOrAlias: "mainnet", blockNumber: 19_662_269 }); + SetUp.setUp(); + (SY, PT, YT) = IPMarket(market).readTokens(); + pendleMarketState = market.readState(address(router)); + coveredCall = new SYCoveredCall(address(dfmm)); + solver = new SYCoveredCallSolver(address(coveredCall)); + timeToExpiry = pendleMarketState.expiry - block.timestamp; + pendleRateScalar = pendleMarketState._getRateScalar(timeToExpiry); + + pendleRateAnchor = pendleMarketState.totalPt._getRateAnchor( + pendleMarketState.lastLnImpliedRate, + pendleMarketState.totalSy, + pendleRateScalar, + timeToExpiry + ); + + deal(wstETH, address(this), 1_000_000e18); + + mintSY(100_000 ether); + mintPtYt(50_000 ether); + + IERC20(wstETH).approve(address(dfmm), type(uint256).max); + IERC20(SY).approve(address(dfmm), type(uint256).max); + IERC20(PT).approve(address(dfmm), type(uint256).max); + IERC20(YT).approve(address(dfmm), type(uint256).max); + + IERC20(wstETH).approve(address(router), type(uint256).max); + IERC20(SY).approve(address(router), type(uint256).max); + IERC20(PT).approve(address(router), type(uint256).max); + IERC20(YT).approve(address(router), type(uint256).max); + IERC20(market).approve(address(router), type(uint256).max); + } + + modifier init() { + vm.warp(0); + + address[] memory tokens = new address[](2); + tokens[0] = address(tokenX); + tokens[1] = address(tokenY); + + SYCoveredCallParams memory defaultParams = SYCoveredCallParams({ + mean: uint256(pendleRateAnchor), + width: 0.1 ether, + maturity: PT.expiry(), + swapFee: TEST_SWAP_FEE, + lastTimestamp: block.timestamp, + controller: address(this), + SY: SY, + PT: PT, + YT: YT + }); + + bytes memory initialPoolData = solver.getInitialPoolDataGivenX( + defaultReserveX, defaultPrice, defaultParams + ); + + InitParams memory defaultInitParams = InitParams({ + name: "", + symbol: "", + strategy: address(coveredCall), + tokens: tokens, + data: initialPoolData, + feeCollector: address(0), + controllerFee: 0 + }); + + (POOL_ID,,) = dfmm.init(defaultInitParams); + + _; + } +} diff --git a/test/SYCoveredCall/unit/Swap.t.sol b/test/SYCoveredCall/unit/Swap.t.sol new file mode 100644 index 00000000..0b35f2ef --- /dev/null +++ b/test/SYCoveredCall/unit/Swap.t.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "./SetUp.sol"; +import { computeTradingFunction } from "src/SYCoveredCall/SYCoveredCallMath.sol"; +import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; + +contract SYCoveredCallSwapTest is SYCoveredCallSetUp { + using FixedPointMathLib for uint256; + + function test_SYCoveredCall_swap_SwapsXforY() public init { + uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); + uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); + + uint256 preUserBalanceX = tokenX.balanceOf(address(this)); + uint256 preUserBalanceY = tokenY.balanceOf(address(this)); + + uint256 amountIn = 0.1 ether; + bool swapXForY = true; + + (bool valid,,, bytes memory payload) = + solver.simulateSwap(POOL_ID, swapXForY, amountIn); + assertEq(valid, true); + + (,, uint256 inputAmount, uint256 outputAmount) = + dfmm.swap(POOL_ID, address(this), payload, ""); + assertEq(tokenX.balanceOf(address(dfmm)), preDfmmBalanceX + inputAmount); + assertEq( + tokenY.balanceOf(address(dfmm)), preDfmmBalanceY - outputAmount + ); + assertEq(tokenX.balanceOf(address(this)), preUserBalanceX - inputAmount); + assertEq( + tokenY.balanceOf(address(this)), preUserBalanceY + outputAmount + ); + } + + function test_SYCoveredCall_swap_SwapsXforY_WarpToMaturity() + public + init_no_fee + { + vm.warp(370 days); + uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); + uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); + + uint256 preUserBalanceX = tokenX.balanceOf(address(this)); + uint256 preUserBalanceY = tokenY.balanceOf(address(this)); + + uint256 amountIn = 99.9999999 ether; + bool swapXForY = true; + + (bool valid, uint256 amountOut,, bytes memory payload) = + solver.simulateSwap(POOL_ID, swapXForY, amountIn); + assertEq(valid, true); + + console2.log("out", amountOut); + + (,, uint256 inputAmount, uint256 outputAmount) = + dfmm.swap(POOL_ID, address(this), payload, ""); + assertEq(tokenX.balanceOf(address(dfmm)), preDfmmBalanceX + inputAmount); + assertEq( + tokenY.balanceOf(address(dfmm)), preDfmmBalanceY - outputAmount + ); + assertEq(tokenX.balanceOf(address(this)), preUserBalanceX - inputAmount); + assertEq( + tokenY.balanceOf(address(this)), preUserBalanceY + outputAmount + ); + } + + function test_SYCoveredCall_swap_SwapsYforX() public init { + uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); + uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); + + uint256 preUserBalanceX = tokenX.balanceOf(address(this)); + uint256 preUserBalanceY = tokenY.balanceOf(address(this)); + + uint256 amountIn = 0.1 ether; + bool swapXForY = false; + + (bool valid,,, bytes memory payload) = + solver.simulateSwap(POOL_ID, swapXForY, amountIn); + assertEq(valid, true); + (,, uint256 inputAmount, uint256 outputAmount) = + dfmm.swap(POOL_ID, address(this), payload, ""); + + assertEq(tokenY.balanceOf(address(dfmm)), preDfmmBalanceY + inputAmount); + assertEq( + tokenX.balanceOf(address(dfmm)), preDfmmBalanceX - outputAmount + ); + + assertEq(tokenY.balanceOf(address(this)), preUserBalanceY - inputAmount); + assertEq( + tokenX.balanceOf(address(this)), preUserBalanceX + outputAmount + ); + } + + // TODO: force payload to yield negative invariant and assert on revert + function test_SYCoveredCall_swap_RevertsIfInvariantNegative() public init { + uint256 amountIn = 0.23 ether; + + (uint256[] memory preReserves, uint256 preTotalLiquidity) = + solver.getReservesAndLiquidity(POOL_ID); + + SYCoveredCallParams memory poolParams = solver.getPoolParams(POOL_ID); + uint256 startL = solver.getNextLiquidity( + POOL_ID, preReserves[0], preReserves[1], preTotalLiquidity + ); + uint256 deltaLiquidity = + amountIn.mulWadUp(poolParams.swapFee).divWadUp(poolParams.mean); + + uint256 ry = preReserves[1] + amountIn; + uint256 L = startL + deltaLiquidity; + uint256 approxPrice = solver.getPriceGivenYL(POOL_ID, ry, L); + + uint256 rx = solver.getNextReserveX(POOL_ID, ry, L, approxPrice); + + int256 invariant = computeTradingFunction(rx, ry, L, poolParams); + while (invariant >= 0) { + rx -= 1; + invariant = computeTradingFunction(rx, ry, L, poolParams); + } + + console2.log(invariant); + + uint256 amountOut = preReserves[0] - rx; + + bytes memory payload = + abi.encode(1, 0, amountIn, amountOut, deltaLiquidity); + + vm.expectRevert(); + dfmm.swap(POOL_ID, address(this), payload, ""); + } + + function test_SYCoveredCall_swap_ChargesCorrectFeesYIn() public deep { + uint256 amountIn = 1 ether; + bool swapXForY = false; + + (bool valid,,, bytes memory payload) = + solver.simulateSwap(POOL_ID, swapXForY, amountIn); + + (,, uint256 inputAmount, uint256 outputAmount) = + dfmm.swap(POOL_ID, address(this), payload, ""); + + console2.log(inputAmount); + console2.log(outputAmount); + } + + function test_SYCoveredCall_swap_ChargesCorrectFeesXIn() public deep { + uint256 amountIn = 1 ether; + bool swapXForY = true; + + (bool valid,,, bytes memory payload) = + solver.simulateSwap(POOL_ID, swapXForY, amountIn); + + (,, uint256 inputAmount, uint256 outputAmount) = + dfmm.swap(POOL_ID, address(this), payload, ""); + + console2.log(inputAmount); + console2.log(outputAmount); + } +} From 3ec0b5378e414075ed57c27dec5617434a975c37 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Fri, 19 Apr 2024 17:11:20 -0400 Subject: [PATCH 09/14] stack2deep --- src/ConstantSum/ConstantSumSolver.sol | 2 +- src/CoveredCall/CoveredCall.sol | 2 +- src/CoveredCall/CoveredCallMath.sol | 4 +- src/CoveredCall/CoveredCallSolver.sol | 4 +- src/DFMM.sol | 6 +-- src/GeometricMean/GeometricMeanSolver.sol | 2 +- src/LogNormal/LogNormalSolver.sol | 2 +- .../NTokenGeometricMeanSolver.sol | 2 +- src/PairStrategy.sol | 2 +- test/CoveredCall/unit/Init.t.sol | 2 +- test/CoveredCall/unit/SetUp.sol | 12 +++--- test/SYCoveredCall/unit/Swap.t.sol | 39 ++----------------- test/utils/MockStrategy.sol | 16 ++++++-- 13 files changed, 37 insertions(+), 58 deletions(-) diff --git a/src/ConstantSum/ConstantSumSolver.sol b/src/ConstantSum/ConstantSumSolver.sol index c6b7f252..8772b86d 100644 --- a/src/ConstantSum/ConstantSumSolver.sol +++ b/src/ConstantSum/ConstantSumSolver.sol @@ -78,7 +78,7 @@ contract ConstantSumSolver { swapData = abi.encode(1, 0, amountIn, amountOut); } - (bool valid,,,,,,) = IStrategy(strategy).validateSwap( + (bool valid,,,,,,,) = IStrategy(strategy).validateSwap( address(this), poolId, pool, swapData ); return (valid, amountOut, swapData); diff --git a/src/CoveredCall/CoveredCall.sol b/src/CoveredCall/CoveredCall.sol index 3b20cefa..dd9949bb 100644 --- a/src/CoveredCall/CoveredCall.sol +++ b/src/CoveredCall/CoveredCall.sol @@ -99,7 +99,7 @@ contract CoveredCall is PairStrategy { (reserves, totalLiquidity, params) = abi.decode(data, (uint256[], uint256, CoveredCallParams)); - params.timestamp = block.timestamp; + params.lastTimestamp = block.timestamp; if (params.mean < MIN_WIDTH || params.mean > MAX_MEAN) { revert InvalidMean(); diff --git a/src/CoveredCall/CoveredCallMath.sol b/src/CoveredCall/CoveredCallMath.sol index 39b8ff36..6226414a 100644 --- a/src/CoveredCall/CoveredCallMath.sol +++ b/src/CoveredCall/CoveredCallMath.sol @@ -31,10 +31,10 @@ function computeTradingFunction( } function computeTau(CoveredCallParams memory params) pure returns (uint256) { - if (params.timestamp >= params.maturity) { + if (params.lastTimestamp >= params.maturity) { return 0; } else { - return ONE * (params.maturity - params.timestamp) / YEAR; + return ONE * (params.maturity - params.lastTimestamp) / YEAR; } } diff --git a/src/CoveredCall/CoveredCallSolver.sol b/src/CoveredCall/CoveredCallSolver.sol index 7744071f..64073807 100644 --- a/src/CoveredCall/CoveredCallSolver.sol +++ b/src/CoveredCall/CoveredCallSolver.sol @@ -72,7 +72,7 @@ contract CoveredCallSolver { uint256 timestamp ) public view returns (CoveredCallParams memory) { CoveredCallParams memory params = getPoolParams(poolId); - params.timestamp = timestamp; + params.lastTimestamp = timestamp; return params; } @@ -305,7 +305,7 @@ contract CoveredCallSolver { } uint256 poolId = poolId; - (bool valid,,,,,,) = IStrategy(strategy).validateSwap( + (bool valid,,,,,,,) = IStrategy(strategy).validateSwap( address(this), poolId, pool, swapData ); diff --git a/src/DFMM.sol b/src/DFMM.sol index 66ae70a2..302772f2 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -215,6 +215,7 @@ contract DFMM is IDFMM { uint256 amountIn; uint256 amountOut; uint256 deltaLiquidity; + bytes postSwapHookData; } /// @inheritdoc IDFMM @@ -225,7 +226,6 @@ contract DFMM is IDFMM { bytes calldata callbackData ) external payable lock returns (address, address, uint256, uint256) { SwapState memory state; - bytes memory postSwapHookData; ( state.valid, @@ -235,7 +235,7 @@ contract DFMM is IDFMM { state.amountIn, state.amountOut, state.deltaLiquidity, - postSwapHookData + state.postSwapHookData ) = IStrategy(_pools[poolId].strategy).validateSwap( msg.sender, poolId, _pools[poolId], data ); @@ -267,7 +267,7 @@ contract DFMM is IDFMM { _transfer(state.tokenOut, recipient, state.amountOut); IStrategy(_pools[poolId].strategy).postSwapHook( - msg.sender, poolId, _pools[poolId], postSwapHookData + msg.sender, poolId, _pools[poolId], state.postSwapHookData ); // If the callbackData is empty, do a regular `_transferFrom()` call, as in the other operations. diff --git a/src/GeometricMean/GeometricMeanSolver.sol b/src/GeometricMean/GeometricMeanSolver.sol index fc87794d..477f7adf 100644 --- a/src/GeometricMean/GeometricMeanSolver.sol +++ b/src/GeometricMean/GeometricMeanSolver.sol @@ -214,7 +214,7 @@ contract GeometricMeanSolver { bytes memory swapData = abi.encode(tokenInIndex, tokenOutIndex, amountIn, state.amountOut); - (bool valid,,,,,,) = IStrategy(strategy).validateSwap( + (bool valid,,,,,,,) = IStrategy(strategy).validateSwap( address(this), poolId, pool, swapData ); diff --git a/src/LogNormal/LogNormalSolver.sol b/src/LogNormal/LogNormalSolver.sol index 9266fc85..54ca31c1 100644 --- a/src/LogNormal/LogNormalSolver.sol +++ b/src/LogNormal/LogNormalSolver.sol @@ -316,7 +316,7 @@ contract LogNormalSolver { } uint256 poolId = poolId; - (bool valid,,,,,,) = IStrategy(strategy).validateSwap( + (bool valid,,,,,,,) = IStrategy(strategy).validateSwap( address(this), poolId, pool, swapData ); return ( diff --git a/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol b/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol index c9f37212..dc378f78 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol @@ -120,7 +120,7 @@ contract NTokenGeometricMeanSolver { bytes memory swapData = abi.encode(tokenInIndex, tokenOutIndex, amountIn, state.amountOut); - (bool valid,,,,,,) = IStrategy(strategy).validateSwap( + (bool valid,,,,,,,) = IStrategy(strategy).validateSwap( address(this), poolId, pool, swapData ); diff --git a/src/PairStrategy.sol b/src/PairStrategy.sol index 40ca25cf..18a44dfb 100644 --- a/src/PairStrategy.sol +++ b/src/PairStrategy.sol @@ -162,7 +162,7 @@ abstract contract PairStrategy is IStrategy { uint256, Pool memory, bytes calldata - ) external onlyDFMM { } + ) external virtual onlyDFMM { } /// @inheritdoc IStrategy function getPoolParams(uint256 poolId) diff --git a/test/CoveredCall/unit/Init.t.sol b/test/CoveredCall/unit/Init.t.sol index 2b529e94..e76dd4fa 100644 --- a/test/CoveredCall/unit/Init.t.sol +++ b/test/CoveredCall/unit/Init.t.sol @@ -25,7 +25,7 @@ contract CoveredCallInitTest is CoveredCallSetUp { width: 0.1 ether, maturity: YEAR * 2, swapFee: FEE, - timestamp: block.timestamp, + lastTimestamp: block.timestamp, controller: address(this) }); diff --git a/test/CoveredCall/unit/SetUp.sol b/test/CoveredCall/unit/SetUp.sol index 13659447..ebd8daf7 100644 --- a/test/CoveredCall/unit/SetUp.sol +++ b/test/CoveredCall/unit/SetUp.sol @@ -41,7 +41,7 @@ contract CoveredCallSetUp is SetUp { width: 0.1 ether, maturity: YEAR, swapFee: TEST_SWAP_FEE, - timestamp: block.timestamp, + lastTimestamp: block.timestamp, controller: address(this) }); @@ -76,7 +76,7 @@ contract CoveredCallSetUp is SetUp { width: 0.05 ether, maturity: YEAR * 2, swapFee: FEE, - timestamp: block.timestamp, + lastTimestamp: block.timestamp, controller: address(this) }); @@ -113,7 +113,7 @@ contract CoveredCallSetUp is SetUp { width: 0.00001 ether, maturity: YEAR, swapFee: 0, - timestamp: block.timestamp, + lastTimestamp: block.timestamp, controller: address(this) }); @@ -148,7 +148,7 @@ contract CoveredCallSetUp is SetUp { width: 0.1 ether, maturity: YEAR / 4, swapFee: TEST_SWAP_FEE, - timestamp: block.timestamp, + lastTimestamp: block.timestamp, controller: address(this) }); @@ -183,7 +183,7 @@ contract CoveredCallSetUp is SetUp { width: 0.25 ether, maturity: YEAR, swapFee: TEST_SWAP_FEE, - timestamp: block.timestamp, + lastTimestamp: block.timestamp, controller: address(this) }); @@ -214,7 +214,7 @@ contract CoveredCallSetUp is SetUp { width: 0, maturity: YEAR, swapFee: TEST_SWAP_FEE, - timestamp: block.timestamp, + lastTimestamp: block.timestamp, controller: address(this) }); diff --git a/test/SYCoveredCall/unit/Swap.t.sol b/test/SYCoveredCall/unit/Swap.t.sol index 0b35f2ef..b28c17d6 100644 --- a/test/SYCoveredCall/unit/Swap.t.sol +++ b/test/SYCoveredCall/unit/Swap.t.sol @@ -19,7 +19,7 @@ contract SYCoveredCallSwapTest is SYCoveredCallSetUp { bool swapXForY = true; (bool valid,,, bytes memory payload) = - solver.simulateSwap(POOL_ID, swapXForY, amountIn); + solver.simulateSwap(POOL_ID, swapXForY, amountIn, block.timestamp); assertEq(valid, true); (,, uint256 inputAmount, uint256 outputAmount) = @@ -34,10 +34,7 @@ contract SYCoveredCallSwapTest is SYCoveredCallSetUp { ); } - function test_SYCoveredCall_swap_SwapsXforY_WarpToMaturity() - public - init_no_fee - { + function test_SYCoveredCall_swap_SwapsXforY_WarpToMaturity() public init { vm.warp(370 days); uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); @@ -49,7 +46,7 @@ contract SYCoveredCallSwapTest is SYCoveredCallSetUp { bool swapXForY = true; (bool valid, uint256 amountOut,, bytes memory payload) = - solver.simulateSwap(POOL_ID, swapXForY, amountIn); + solver.simulateSwap(POOL_ID, swapXForY, amountIn, block.timestamp); assertEq(valid, true); console2.log("out", amountOut); @@ -77,7 +74,7 @@ contract SYCoveredCallSwapTest is SYCoveredCallSetUp { bool swapXForY = false; (bool valid,,, bytes memory payload) = - solver.simulateSwap(POOL_ID, swapXForY, amountIn); + solver.simulateSwap(POOL_ID, swapXForY, amountIn, block.timestamp); assertEq(valid, true); (,, uint256 inputAmount, uint256 outputAmount) = dfmm.swap(POOL_ID, address(this), payload, ""); @@ -129,32 +126,4 @@ contract SYCoveredCallSwapTest is SYCoveredCallSetUp { vm.expectRevert(); dfmm.swap(POOL_ID, address(this), payload, ""); } - - function test_SYCoveredCall_swap_ChargesCorrectFeesYIn() public deep { - uint256 amountIn = 1 ether; - bool swapXForY = false; - - (bool valid,,, bytes memory payload) = - solver.simulateSwap(POOL_ID, swapXForY, amountIn); - - (,, uint256 inputAmount, uint256 outputAmount) = - dfmm.swap(POOL_ID, address(this), payload, ""); - - console2.log(inputAmount); - console2.log(outputAmount); - } - - function test_SYCoveredCall_swap_ChargesCorrectFeesXIn() public deep { - uint256 amountIn = 1 ether; - bool swapXForY = true; - - (bool valid,,, bytes memory payload) = - solver.simulateSwap(POOL_ID, swapXForY, amountIn); - - (,, uint256 inputAmount, uint256 outputAmount) = - dfmm.swap(POOL_ID, address(this), payload, ""); - - console2.log(inputAmount); - console2.log(outputAmount); - } } diff --git a/test/utils/MockStrategy.sol b/test/utils/MockStrategy.sol index a62afeba..0dfd0e9f 100644 --- a/test/utils/MockStrategy.sol +++ b/test/utils/MockStrategy.sol @@ -96,7 +96,8 @@ contract MockStrategy is IStrategy { uint256 tokenOutIndex, uint256 amountIn, uint256 amountOut, - uint256 deltaLiquidity + uint256 deltaLiquidity, + bytes memory params ) { ( @@ -106,12 +107,21 @@ contract MockStrategy is IStrategy { tokenOutIndex, amountIn, amountOut, - deltaLiquidity + deltaLiquidity, + params ) = abi.decode( - data, (bool, int256, uint256, uint256, uint256, uint256, uint256) + data, + (bool, int256, uint256, uint256, uint256, uint256, uint256, bytes) ); } + function postSwapHook( + address, + uint256, + Pool calldata, + bytes calldata + ) external pure override { } + function update( address sender, uint256 poolId, From d221e586a0f866023555aaa6eebd7cdbe4a28503 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 22 Apr 2024 13:33:36 -0400 Subject: [PATCH 10/14] fix SY allo/deallo tests --- foundry.toml | 1 + src/DFMM.sol | 13 +++++++------ src/SYCoveredCall/SYCoveredCall.sol | 17 ++++++++++++----- src/SYCoveredCall/SYCoveredCallSolver.sol | 22 ++++++++++++---------- test/SYCoveredCall/unit/SetUp.sol | 5 +---- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/foundry.toml b/foundry.toml index ca26581c..d7461674 100644 --- a/foundry.toml +++ b/foundry.toml @@ -24,6 +24,7 @@ number_underscore = "thousands" [rpc_endpoints] local = "http://localhost:8545" optimism_sepolia = "${OPTIMISM_SEPOLIA_RPC_URL}" +mainnet = "${MAINNET_RPC_URL}" [etherscan] optimism_sepolia = { key = "${ETHERSCAN_API_KEY}" } diff --git a/src/DFMM.sol b/src/DFMM.sol index 302772f2..d72ac394 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -13,6 +13,7 @@ import { downscaleUp } from "./lib/ScalingLib.sol"; import { LPToken } from "./LPToken.sol"; +import "forge-std/console2.sol"; /** * @title DFMM @@ -218,7 +219,6 @@ contract DFMM is IDFMM { bytes postSwapHookData; } - /// @inheritdoc IDFMM function swap( uint256 poolId, address recipient, @@ -240,6 +240,8 @@ contract DFMM is IDFMM { msg.sender, poolId, _pools[poolId], data ); + uint256 poolId = poolId; + if (!state.valid) revert InvalidInvariant(state.invariant); if (_pools[poolId].controllerFee > 0) { @@ -258,11 +260,6 @@ contract DFMM is IDFMM { state.tokenIn = _pools[poolId].tokens[state.tokenInIndex]; state.tokenOut = _pools[poolId].tokens[state.tokenOutIndex]; - address[] memory tokens = new address[](1); - tokens[0] = state.tokenIn; - uint256[] memory amounts = new uint256[](1); - amounts[0] = state.amountIn; - // Optimistically transfer the output tokens to the recipient. _transfer(state.tokenOut, recipient, state.amountOut); @@ -272,6 +269,10 @@ contract DFMM is IDFMM { // If the callbackData is empty, do a regular `_transferFrom()` call, as in the other operations. if (callbackData.length == 0) { + address[] memory tokens = new address[](1); + tokens[0] = state.tokenIn; + uint256[] memory amounts = new uint256[](1); + amounts[0] = state.amountIn; _transferFrom(tokens, amounts); } else { // Otherwise, execute the callback and assert the input amount has been paid diff --git a/src/SYCoveredCall/SYCoveredCall.sol b/src/SYCoveredCall/SYCoveredCall.sol index 5ea2ea4f..d6857e96 100644 --- a/src/SYCoveredCall/SYCoveredCall.sol +++ b/src/SYCoveredCall/SYCoveredCall.sol @@ -23,6 +23,7 @@ import { EPSILON } from "src/lib/StrategyLib.sol"; import { IPPrincipalToken } from "pendle/interfaces/IPPrincipalToken.sol"; import { IStandardizedYield } from "pendle/interfaces/IStandardizedYield.sol"; import { IPYieldToken } from "pendle/interfaces/IPYieldToken.sol"; +import "forge-std/console2.sol"; enum UpdateCode { Invalid, @@ -125,11 +126,15 @@ contract SYCoveredCall is PairStrategy { int256 tau = int256(computeTau(params)); - if (PT.SY() != address(SY)) { + console2.log("got here1"); + console2.log("pt.sy", params.PT.SY()); + console2.log("sy", address(params.SY)); + if (params.PT.SY() != address(params.SY)) { revert InvalidPair(); } + console2.log("got here2"); - if (PT.expiry() <= block.timestamp) { + if (params.PT.expiry() <= block.timestamp) { revert InvalidMaturity(); } @@ -141,9 +146,9 @@ contract SYCoveredCall is PairStrategy { revert InvalidReservesLength(); } - internalParams[poolId].SY = SY; - internalParams[poolId].PT = PT; - internalParams[poolId].YT = IPYieldToken(PT.YT()); + internalParams[poolId].SY = params.SY; + internalParams[poolId].PT = params.PT; + internalParams[poolId].YT = IPYieldToken(params.PT.YT()); internalParams[poolId].maturity = internalParams[poolId].PT.expiry(); internalParams[poolId].mean = params.mean; @@ -217,6 +222,7 @@ contract SYCoveredCall is PairStrategy { params = getPoolParams(poolId); SYCoveredCallParams memory ccParams = abi.decode(params, (SYCoveredCallParams)); + console2.log("got here"); uint256 computedL; uint256 swapTimestamp; @@ -230,6 +236,7 @@ contract SYCoveredCall is PairStrategy { ) = abi.decode( data, (uint256, uint256, uint256, uint256, uint256, uint256) ); + console2.log("got here2"); if ( swapTimestamp < internalParams[poolId].lastTimestamp diff --git a/src/SYCoveredCall/SYCoveredCallSolver.sol b/src/SYCoveredCall/SYCoveredCallSolver.sol index bee19b03..4546890b 100644 --- a/src/SYCoveredCall/SYCoveredCallSolver.sol +++ b/src/SYCoveredCall/SYCoveredCallSolver.sol @@ -217,7 +217,6 @@ contract SYCoveredCallSolver { uint256 fees; } - /// @dev Estimates a swap's reserves and adjustments and returns its validity. function simulateSwap( uint256 poolId, bool swapXIn, @@ -238,17 +237,21 @@ contract SYCoveredCallSolver { poolParams.mean = computeKGivenLastPrice( preReserves[0], preTotalLiquidity, poolParams ); + + uint256 poolId = poolId; + uint256 swapAmountIn = amountIn; + bool swapXToY = swapXIn; { - if (swapXIn) { + if (swapXToY) { state.deltaLiquidity = computeDeltaLXIn( - amountIn, + swapAmountIn, preReserves[0], preReserves[1], preTotalLiquidity, poolParams ); - endReserves.rx = preReserves[0] + amountIn; + endReserves.rx = preReserves[0] + swapAmountIn; endReserves.L = startComputedL + state.deltaLiquidity; uint256 approxPrice = getPriceGivenXL(poolId, endReserves.rx, endReserves.L); @@ -264,14 +267,14 @@ contract SYCoveredCallSolver { state.amountOut = preReserves[1] - endReserves.ry; } else { state.deltaLiquidity = computeDeltaLYIn( - amountIn, + swapAmountIn, preReserves[0], preReserves[1], preTotalLiquidity, poolParams ); - endReserves.ry = preReserves[1] + amountIn; + endReserves.ry = preReserves[1] + swapAmountIn; endReserves.L = startComputedL + state.deltaLiquidity; uint256 approxPrice = getPriceGivenYL(poolId, endReserves.ry, endReserves.L); @@ -294,15 +297,14 @@ contract SYCoveredCallSolver { bytes memory swapData; - if (swapXIn) { + if (swapXToY) { swapData = - abi.encode(0, 1, amountIn, state.amountOut, startComputedL); + abi.encode(0, 1, swapAmountIn, state.amountOut, startComputedL); } else { swapData = - abi.encode(1, 0, amountIn, state.amountOut, startComputedL); + abi.encode(1, 0, swapAmountIn, state.amountOut, startComputedL); } - uint256 poolId = poolId; (bool valid,,,,,,,) = IStrategy(strategy).validateSwap( address(this), poolId, pool, swapData ); diff --git a/test/SYCoveredCall/unit/SetUp.sol b/test/SYCoveredCall/unit/SetUp.sol index d7cfe4ae..3756f9c2 100644 --- a/test/SYCoveredCall/unit/SetUp.sol +++ b/test/SYCoveredCall/unit/SetUp.sol @@ -51,10 +51,7 @@ contract SYCoveredCallSetUp is SetUp { function mintPtYt(uint256 amount) public { SY.transfer(address(YT), amount); - uint256 pyOut = YT.mintPY(address(this), address(this)); - uint256 ptBal = PT.balanceOf(address(this)); - uint256 ytBal = YT.balanceOf(address(this)); - uint256 syBal = SY.balanceOf(address(this)); + YT.mintPY(address(this), address(this)); } function setUp() public override { From 6cc2d7e08de82317ba9bf72316116dbcaf777d4b Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 22 Apr 2024 14:33:08 -0400 Subject: [PATCH 11/14] fix SY swap tests --- src/SYCoveredCall/SYCoveredCall.sol | 28 +++++++++----------- src/SYCoveredCall/SYCoveredCallSolver.sol | 25 +++++++++++++++--- test/SYCoveredCall/unit/SetUp.sol | 5 ++-- test/SYCoveredCall/unit/Swap.t.sol | 32 ++++++++++++++++++++--- 4 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/SYCoveredCall/SYCoveredCall.sol b/src/SYCoveredCall/SYCoveredCall.sol index d6857e96..51afd74e 100644 --- a/src/SYCoveredCall/SYCoveredCall.sol +++ b/src/SYCoveredCall/SYCoveredCall.sol @@ -5,6 +5,7 @@ import { Pool } from "src/interfaces/IDFMM.sol"; import { PairStrategy, IStrategy } from "src/PairStrategy.sol"; import { IDFMM } from "src/interfaces/IDFMM.sol"; import { DynamicParamLib, DynamicParam } from "src/lib/DynamicParamLib.sol"; +import { SignedWadMathLib } from "src/lib/SignedWadMath.sol"; import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; import { computeTradingFunction, @@ -12,7 +13,7 @@ import { computeDeltaGivenDeltaLRoundDown, computeDeltaLXIn, computeDeltaLYIn, - computeTau, + computePriceGivenX, computeKGivenLastPrice } from "src/SYCoveredCall/SYCoveredCallMath.sol"; import { @@ -24,6 +25,7 @@ import { IPPrincipalToken } from "pendle/interfaces/IPPrincipalToken.sol"; import { IStandardizedYield } from "pendle/interfaces/IStandardizedYield.sol"; import { IPYieldToken } from "pendle/interfaces/IPYieldToken.sol"; import "forge-std/console2.sol"; +import { Gaussian } from "solstat/Gaussian.sol"; enum UpdateCode { Invalid, @@ -120,19 +122,11 @@ contract SYCoveredCall is PairStrategy { (reserves, totalLiquidity, params) = abi.decode(data, (uint256[], uint256, SYCoveredCallParams)); - IStandardizedYield SY = IStandardizedYield(pool.tokens[0]); - IPPrincipalToken PT = IPPrincipalToken(pool.tokens[1]); params.lastTimestamp = block.timestamp; - int256 tau = int256(computeTau(params)); - - console2.log("got here1"); - console2.log("pt.sy", params.PT.SY()); - console2.log("sy", address(params.SY)); if (params.PT.SY() != address(params.SY)) { revert InvalidPair(); } - console2.log("got here2"); if (params.PT.expiry() <= block.timestamp) { revert InvalidMaturity(); @@ -155,6 +149,7 @@ contract SYCoveredCall is PairStrategy { internalParams[poolId].width = params.width; internalParams[poolId].swapFee = params.swapFee; internalParams[poolId].controller = params.controller; + internalParams[poolId].lastTimestamp = block.timestamp; invariant = tradingFunction(reserves, totalLiquidity, abi.encode(params)); @@ -222,7 +217,6 @@ contract SYCoveredCall is PairStrategy { params = getPoolParams(poolId); SYCoveredCallParams memory ccParams = abi.decode(params, (SYCoveredCallParams)); - console2.log("got here"); uint256 computedL; uint256 swapTimestamp; @@ -236,7 +230,9 @@ contract SYCoveredCall is PairStrategy { ) = abi.decode( data, (uint256, uint256, uint256, uint256, uint256, uint256) ); - console2.log("got here2"); + + console2.log("swapTimestamp", swapTimestamp); + console2.log("block.timestamp", block.timestamp); if ( swapTimestamp < internalParams[poolId].lastTimestamp @@ -249,31 +245,33 @@ contract SYCoveredCall is PairStrategy { // if timestamp is valid, append it to the poolParams for validation check ccParams.lastTimestamp = swapTimestamp; - // compute new K ccParams.mean = computeKGivenLastPrice(pool.reserves[0], computedL, ccParams); int256 computedInvariant = - tradingFunction(pool.reserves, computedL, abi.encode(params)); + tradingFunction(pool.reserves, computedL, abi.encode(ccParams)); + console2.log("got here"); if (computedInvariant < 0 || computedInvariant > EPSILON) { revert InvalidComputedLiquidity(computedInvariant); } + console2.log("now we compute dl"); deltaLiquidity = _computeSwapDeltaLiquidity( pool, - abi.encode(params), + abi.encode(ccParams), tokenInIndex, tokenOutIndex, amountIn, amountOut ); + console2.log("deltaLiquidity", deltaLiquidity); pool.reserves[tokenInIndex] += amountIn; pool.reserves[tokenOutIndex] -= amountOut; invariant = tradingFunction( - pool.reserves, computedL + deltaLiquidity, abi.encode(params) + pool.reserves, computedL + deltaLiquidity, abi.encode(ccParams) ); params = abi.encode(ccParams); diff --git a/src/SYCoveredCall/SYCoveredCallSolver.sol b/src/SYCoveredCall/SYCoveredCallSolver.sol index 4546890b..c51b8d38 100644 --- a/src/SYCoveredCall/SYCoveredCallSolver.sol +++ b/src/SYCoveredCall/SYCoveredCallSolver.sol @@ -215,6 +215,7 @@ contract SYCoveredCallSolver { uint256 amountOut; uint256 deltaLiquidity; uint256 fees; + uint256 timestamp; } function simulateSwap( @@ -230,6 +231,10 @@ contract SYCoveredCallSolver { getPoolParamsCustomTimestamp(poolId, timestamp); SimulateSwapState memory state; + state.timestamp = timestamp; + console2.log("preTotalLiquidity", preTotalLiquidity); + console2.log("preReserves[0]", preReserves[0]); + console2.log("preReserves[1]", preReserves[1]); uint256 startComputedL = getNextLiquidity( poolId, preReserves[0], preReserves[1], preTotalLiquidity @@ -298,11 +303,23 @@ contract SYCoveredCallSolver { bytes memory swapData; if (swapXToY) { - swapData = - abi.encode(0, 1, swapAmountIn, state.amountOut, startComputedL); + swapData = abi.encode( + 0, + 1, + swapAmountIn, + state.amountOut, + startComputedL, + state.timestamp + ); } else { - swapData = - abi.encode(1, 0, swapAmountIn, state.amountOut, startComputedL); + swapData = abi.encode( + 1, + 0, + swapAmountIn, + state.amountOut, + startComputedL, + state.timestamp + ); } (bool valid,,,,,,,) = IStrategy(strategy).validateSwap( diff --git a/test/SYCoveredCall/unit/SetUp.sol b/test/SYCoveredCall/unit/SetUp.sol index 3756f9c2..3a4411e8 100644 --- a/test/SYCoveredCall/unit/SetUp.sol +++ b/test/SYCoveredCall/unit/SetUp.sol @@ -89,8 +89,6 @@ contract SYCoveredCallSetUp is SetUp { } modifier init() { - vm.warp(0); - address[] memory tokens = new address[](2); tokens[0] = address(tokenX); tokens[1] = address(tokenY); @@ -99,7 +97,7 @@ contract SYCoveredCallSetUp is SetUp { mean: uint256(pendleRateAnchor), width: 0.1 ether, maturity: PT.expiry(), - swapFee: TEST_SWAP_FEE, + swapFee: 0.0005 ether, lastTimestamp: block.timestamp, controller: address(this), SY: SY, @@ -120,6 +118,7 @@ contract SYCoveredCallSetUp is SetUp { feeCollector: address(0), controllerFee: 0 }); + console2.log("timestamp", block.timestamp); (POOL_ID,,) = dfmm.init(defaultInitParams); diff --git a/test/SYCoveredCall/unit/Swap.t.sol b/test/SYCoveredCall/unit/Swap.t.sol index b28c17d6..019d374d 100644 --- a/test/SYCoveredCall/unit/Swap.t.sol +++ b/test/SYCoveredCall/unit/Swap.t.sol @@ -34,15 +34,16 @@ contract SYCoveredCallSwapTest is SYCoveredCallSetUp { ); } - function test_SYCoveredCall_swap_SwapsXforY_WarpToMaturity() public init { - vm.warp(370 days); + function test_SYCoveredCall_swap_SwapsXforY_Warp2Days() public init { + vm.warp(block.timestamp + 2 days); + uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); uint256 preUserBalanceX = tokenX.balanceOf(address(this)); uint256 preUserBalanceY = tokenY.balanceOf(address(this)); - uint256 amountIn = 99.9999999 ether; + uint256 amountIn = 0.1 ether; bool swapXForY = true; (bool valid, uint256 amountOut,, bytes memory payload) = @@ -126,4 +127,29 @@ contract SYCoveredCallSwapTest is SYCoveredCallSetUp { vm.expectRevert(); dfmm.swap(POOL_ID, address(this), payload, ""); } + + function _computeDeltaLXIn( + uint256 amountIn, + uint256 rx, + uint256 ry, + uint256 L, + SYCoveredCallParams memory params + ) external view returns (uint256 deltaL) { + uint256 fees = params.swapFee.mulWadUp(amountIn); + uint256 px = computePriceGivenX(rx, L, params); + deltaL = + px.mulWadUp(L).mulWadUp(fees).divWadDown(px.mulWadDown(rx) + ry); + } + + function _computeDeltaLYIn( + uint256 amountIn, + uint256 rx, + uint256 ry, + uint256 L, + SYCoveredCallParams memory params + ) external returns (uint256 deltaL) { + uint256 fees = params.swapFee.mulWadUp(amountIn); + uint256 px = computePriceGivenX(rx, L, params); + deltaL = L.mulWadUp(fees).divWadDown(px.mulWadDown(rx) + ry); + } } From 063c249c95808ff63e15c196627d39b98e831c6c Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 22 Apr 2024 14:34:32 -0400 Subject: [PATCH 12/14] rm console2 --- src/SYCoveredCall/SYCoveredCall.sol | 7 ------- src/SYCoveredCall/SYCoveredCallSolver.sol | 4 ---- 2 files changed, 11 deletions(-) diff --git a/src/SYCoveredCall/SYCoveredCall.sol b/src/SYCoveredCall/SYCoveredCall.sol index 51afd74e..f1bc5f90 100644 --- a/src/SYCoveredCall/SYCoveredCall.sol +++ b/src/SYCoveredCall/SYCoveredCall.sol @@ -24,7 +24,6 @@ import { EPSILON } from "src/lib/StrategyLib.sol"; import { IPPrincipalToken } from "pendle/interfaces/IPPrincipalToken.sol"; import { IStandardizedYield } from "pendle/interfaces/IStandardizedYield.sol"; import { IPYieldToken } from "pendle/interfaces/IPYieldToken.sol"; -import "forge-std/console2.sol"; import { Gaussian } from "solstat/Gaussian.sol"; enum UpdateCode { @@ -231,9 +230,6 @@ contract SYCoveredCall is PairStrategy { data, (uint256, uint256, uint256, uint256, uint256, uint256) ); - console2.log("swapTimestamp", swapTimestamp); - console2.log("block.timestamp", block.timestamp); - if ( swapTimestamp < internalParams[poolId].lastTimestamp || swapTimestamp < block.timestamp - T_EPSILON @@ -250,13 +246,11 @@ contract SYCoveredCall is PairStrategy { int256 computedInvariant = tradingFunction(pool.reserves, computedL, abi.encode(ccParams)); - console2.log("got here"); if (computedInvariant < 0 || computedInvariant > EPSILON) { revert InvalidComputedLiquidity(computedInvariant); } - console2.log("now we compute dl"); deltaLiquidity = _computeSwapDeltaLiquidity( pool, abi.encode(ccParams), @@ -265,7 +259,6 @@ contract SYCoveredCall is PairStrategy { amountIn, amountOut ); - console2.log("deltaLiquidity", deltaLiquidity); pool.reserves[tokenInIndex] += amountIn; pool.reserves[tokenOutIndex] -= amountOut; diff --git a/src/SYCoveredCall/SYCoveredCallSolver.sol b/src/SYCoveredCall/SYCoveredCallSolver.sol index c51b8d38..84192195 100644 --- a/src/SYCoveredCall/SYCoveredCallSolver.sol +++ b/src/SYCoveredCall/SYCoveredCallSolver.sol @@ -35,7 +35,6 @@ import { YEAR, ONE } from "src/SYCoveredCall/SYCoveredCallMath.sol"; -import "forge-std/console2.sol"; contract SYCoveredCallSolver { using FixedPointMathLib for uint256; @@ -232,9 +231,6 @@ contract SYCoveredCallSolver { SimulateSwapState memory state; state.timestamp = timestamp; - console2.log("preTotalLiquidity", preTotalLiquidity); - console2.log("preReserves[0]", preReserves[0]); - console2.log("preReserves[1]", preReserves[1]); uint256 startComputedL = getNextLiquidity( poolId, preReserves[0], preReserves[1], preTotalLiquidity From 55418b828f82e7dce8f364840cc104d013966a34 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 22 Apr 2024 16:56:19 -0400 Subject: [PATCH 13/14] check that K updates for next swap --- test/SYCoveredCall/unit/SetUp.sol | 5 +++-- test/SYCoveredCall/unit/Swap.t.sol | 31 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/test/SYCoveredCall/unit/SetUp.sol b/test/SYCoveredCall/unit/SetUp.sol index 3a4411e8..7a6c8116 100644 --- a/test/SYCoveredCall/unit/SetUp.sol +++ b/test/SYCoveredCall/unit/SetUp.sol @@ -27,7 +27,7 @@ contract SYCoveredCallSetUp is SetUp { uint256 defaultReserveXMil = 1_000_000 ether; uint256 defaultReserveXDeep = ONE * 10_000_000; - uint256 defaultPrice = ONE; + uint256 defaultPrice; uint256 defaultPricePoint9Rate = 0.84167999326 ether; address public constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //real wsteth @@ -105,6 +105,8 @@ contract SYCoveredCallSetUp is SetUp { YT: YT }); + defaultPrice = uint256(pendleRateAnchor); + bytes memory initialPoolData = solver.getInitialPoolDataGivenX( defaultReserveX, defaultPrice, defaultParams ); @@ -118,7 +120,6 @@ contract SYCoveredCallSetUp is SetUp { feeCollector: address(0), controllerFee: 0 }); - console2.log("timestamp", block.timestamp); (POOL_ID,,) = dfmm.init(defaultInitParams); diff --git a/test/SYCoveredCall/unit/Swap.t.sol b/test/SYCoveredCall/unit/Swap.t.sol index 019d374d..5ac5f643 100644 --- a/test/SYCoveredCall/unit/Swap.t.sol +++ b/test/SYCoveredCall/unit/Swap.t.sol @@ -64,6 +64,37 @@ contract SYCoveredCallSwapTest is SYCoveredCallSetUp { ); } + function test_SYCoveredCall_swap_SwapsXforY_Warp2DaysUpdatesK() + public + init + { + vm.warp(block.timestamp + 2 days); + + (uint256[] memory reserves, uint256 L) = + solver.getReservesAndLiquidity(POOL_ID); + SYCoveredCallParams memory params = solver.getPoolParams(POOL_ID); + uint256 initialK = computeKGivenLastPrice(reserves[0], L, params); + uint256 amountIn = 0.1 ether; + bool swapXForY = true; + console2.log("initialK", initialK); + + (bool valid, uint256 amountOut,, bytes memory payload) = + solver.simulateSwap(POOL_ID, swapXForY, amountIn, block.timestamp); + assertEq(valid, true); + + console2.log("out", amountOut); + + (,, uint256 inputAmount, uint256 outputAmount) = + dfmm.swap(POOL_ID, address(this), payload, ""); + + (uint256[] memory nextReserves, uint256 nextL) = + solver.getReservesAndLiquidity(POOL_ID); + SYCoveredCallParams memory nextParams = solver.getPoolParams(POOL_ID); + + uint256 k = computeKGivenLastPrice(nextReserves[0], nextL, nextParams); + console2.log("k", k); + } + function test_SYCoveredCall_swap_SwapsYforX() public init { uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); From c427eaba2568f11344c6df826bc7d7822902ea83 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Fri, 26 Apr 2024 12:19:40 -0400 Subject: [PATCH 14/14] wip --- src/SYCoveredCall/SYCoveredCall.sol | 11 ++- src/SYCoveredCall/SYCoveredCallMath.sol | 4 +- src/SYCoveredCall/SYCoveredCallSolver.sol | 92 +++++++++++------------ test/SYCoveredCall/unit/SetUp.sol | 12 ++- test/SYCoveredCall/unit/Swap.t.sol | 8 +- 5 files changed, 68 insertions(+), 59 deletions(-) diff --git a/src/SYCoveredCall/SYCoveredCall.sol b/src/SYCoveredCall/SYCoveredCall.sol index f1bc5f90..428d4c3a 100644 --- a/src/SYCoveredCall/SYCoveredCall.sol +++ b/src/SYCoveredCall/SYCoveredCall.sol @@ -25,6 +25,7 @@ import { IPPrincipalToken } from "pendle/interfaces/IPPrincipalToken.sol"; import { IStandardizedYield } from "pendle/interfaces/IStandardizedYield.sol"; import { IPYieldToken } from "pendle/interfaces/IPYieldToken.sol"; import { Gaussian } from "solstat/Gaussian.sol"; +import "forge-std/console2.sol"; enum UpdateCode { Invalid, @@ -39,6 +40,7 @@ struct InternalParams { uint256 swapFee; address controller; uint256 lastTimestamp; + uint256 lastImpliedPrice; IStandardizedYield SY; IPPrincipalToken PT; IPYieldToken YT; @@ -52,6 +54,7 @@ struct SYCoveredCallParams { uint256 swapFee; address controller; uint256 lastTimestamp; + uint256 lastImpliedPrice; IStandardizedYield SY; IPPrincipalToken PT; IPYieldToken YT; @@ -149,9 +152,11 @@ contract SYCoveredCall is PairStrategy { internalParams[poolId].swapFee = params.swapFee; internalParams[poolId].controller = params.controller; internalParams[poolId].lastTimestamp = block.timestamp; + internalParams[poolId].lastImpliedPrice = computePriceGivenX(reserves[0], totalLiquidity, params); invariant = tradingFunction(reserves, totalLiquidity, abi.encode(params)); + console2.log("initial invariant after init", invariant); valid = invariant >= 0 && invariant <= EPSILON; } @@ -187,6 +192,7 @@ contract SYCoveredCall is PairStrategy { params.swapFee = internalParams[poolId].swapFee; params.maturity = internalParams[poolId].maturity; params.lastTimestamp = internalParams[poolId].lastTimestamp; + params.lastImpliedPrice = internalParams[poolId].lastImpliedPrice; return abi.encode(params); } @@ -242,7 +248,7 @@ contract SYCoveredCall is PairStrategy { ccParams.lastTimestamp = swapTimestamp; ccParams.mean = - computeKGivenLastPrice(pool.reserves[0], computedL, ccParams); + computeKGivenLastPrice(pool.reserves[0], pool.totalLiquidity, ccParams); int256 computedInvariant = tradingFunction(pool.reserves, computedL, abi.encode(ccParams)); @@ -275,7 +281,7 @@ contract SYCoveredCall is PairStrategy { function postSwapHook( address, uint256 poolId, - Pool memory, + Pool memory pool, bytes memory params ) external override onlyDFMM { SYCoveredCallParams memory ccParams = @@ -283,6 +289,7 @@ contract SYCoveredCall is PairStrategy { internalParams[poolId].lastTimestamp = ccParams.lastTimestamp; internalParams[poolId].mean = ccParams.mean; + internalParams[poolId].lastImpliedPrice = computePriceGivenX(pool.reserves[0], pool.totalLiquidity, ccParams); } /// @inheritdoc IStrategy diff --git a/src/SYCoveredCall/SYCoveredCallMath.sol b/src/SYCoveredCall/SYCoveredCallMath.sol index 94440f9d..4f56a775 100644 --- a/src/SYCoveredCall/SYCoveredCallMath.sol +++ b/src/SYCoveredCall/SYCoveredCallMath.sol @@ -220,8 +220,6 @@ function computeKGivenLastPrice( uint256 L, SYCoveredCallParams memory params ) pure returns (uint256 K) { - uint256 price = computePriceGivenX(rX, L, params); - uint256 tau = computeTau(params); uint256 a = computeHalfSigmaSquaredTau(params.width, tau); // $$\Phi^{-1} (1 - \frac{x}{L})$$ @@ -230,7 +228,7 @@ function computeKGivenLastPrice( b.wadMul(int256(computeSigmaSqrtTau(params.width, tau))) - int256(a) ).expWad(); - K = price.divWadDown(uint256(exp)); + K = params.lastImpliedPrice.divWadDown(uint256(exp)); } function computePriceGivenY( diff --git a/src/SYCoveredCall/SYCoveredCallSolver.sol b/src/SYCoveredCall/SYCoveredCallSolver.sol index 84192195..d7ed4356 100644 --- a/src/SYCoveredCall/SYCoveredCallSolver.sol +++ b/src/SYCoveredCall/SYCoveredCallSolver.sol @@ -35,6 +35,7 @@ import { YEAR, ONE } from "src/SYCoveredCall/SYCoveredCallMath.sol"; +import "forge-std/console2.sol"; contract SYCoveredCallSolver { using FixedPointMathLib for uint256; @@ -170,44 +171,37 @@ contract SYCoveredCallSolver { } function getNextLiquidity( - uint256 poolId, uint256 rx, uint256 ry, - uint256 L - ) public view returns (uint256) { - SYCoveredCallParams memory poolParams = - getPoolParamsCustomTimestamp(poolId, block.timestamp); - - int256 invariant = computeTradingFunction(rx, ry, L, poolParams); - return computeNextLiquidity(rx, ry, invariant, L, poolParams); + uint256 L, + SYCoveredCallParams memory params + ) public pure returns (uint256) { + int256 invariant = computeTradingFunction(rx, ry, L, params); + return computeNextLiquidity(rx, ry, invariant, L, params); } function getNextReserveX( - uint256 poolId, uint256 ry, uint256 L, - uint256 S - ) public view returns (uint256) { - SYCoveredCallParams memory poolParams = - getPoolParamsCustomTimestamp(poolId, block.timestamp); - uint256 approximatedRx = computeXGivenL(L, S, poolParams); + uint256 S, + SYCoveredCallParams memory params + ) public pure returns (uint256) { + uint256 approximatedRx = computeXGivenL(L, S, params); int256 invariant = - computeTradingFunction(approximatedRx, ry, L, poolParams); - return computeNextRx(ry, L, invariant, approximatedRx, poolParams); + computeTradingFunction(approximatedRx, ry, L, params); + return computeNextRx(ry, L, invariant, approximatedRx, params); } function getNextReserveY( - uint256 poolId, uint256 rx, uint256 L, - uint256 S - ) public view returns (uint256) { - SYCoveredCallParams memory poolParams = - getPoolParamsCustomTimestamp(poolId, block.timestamp); - uint256 approximatedRy = computeYGivenL(L, S, poolParams); + uint256 S, + SYCoveredCallParams memory params + ) public pure returns (uint256) { + uint256 approximatedRy = computeYGivenL(L, S, params); int256 invariant = - computeTradingFunction(rx, approximatedRy, L, poolParams); - return computeNextRy(rx, L, invariant, approximatedRy, poolParams); + computeTradingFunction(rx, approximatedRy, L, params); + return computeNextRy(rx, L, invariant, approximatedRy, params); } struct SimulateSwapState { @@ -226,18 +220,24 @@ contract SYCoveredCallSolver { Reserves memory endReserves; (uint256[] memory preReserves, uint256 preTotalLiquidity) = getReservesAndLiquidity(poolId); - SYCoveredCallParams memory poolParams = + SYCoveredCallParams memory params = getPoolParamsCustomTimestamp(poolId, timestamp); SimulateSwapState memory state; state.timestamp = timestamp; + params.lastTimestamp = timestamp; + console2.log("initial K", params.mean); - uint256 startComputedL = getNextLiquidity( - poolId, preReserves[0], preReserves[1], preTotalLiquidity + params.mean = computeKGivenLastPrice( + preReserves[0], preTotalLiquidity, params ); - poolParams.mean = computeKGivenLastPrice( - preReserves[0], preTotalLiquidity, poolParams + console2.log("updated K", params.mean); + + console2.log("preL", preTotalLiquidity); + uint256 startComputedL = getNextLiquidity( + preReserves[0], preReserves[1], preTotalLiquidity, params ); + console2.log("computedL", startComputedL); uint256 poolId = poolId; uint256 swapAmountIn = amountIn; @@ -248,17 +248,17 @@ contract SYCoveredCallSolver { swapAmountIn, preReserves[0], preReserves[1], - preTotalLiquidity, - poolParams + startComputedL, + params ); endReserves.rx = preReserves[0] + swapAmountIn; endReserves.L = startComputedL + state.deltaLiquidity; uint256 approxPrice = - getPriceGivenXL(poolId, endReserves.rx, endReserves.L); + getPriceGivenXL(endReserves.rx, endReserves.L, params); endReserves.ry = getNextReserveY( - poolId, endReserves.rx, endReserves.L, approxPrice + endReserves.rx, endReserves.L, approxPrice, params ); require( @@ -271,17 +271,17 @@ contract SYCoveredCallSolver { swapAmountIn, preReserves[0], preReserves[1], - preTotalLiquidity, - poolParams + startComputedL, + params ); endReserves.ry = preReserves[1] + swapAmountIn; endReserves.L = startComputedL + state.deltaLiquidity; uint256 approxPrice = - getPriceGivenYL(poolId, endReserves.ry, endReserves.L); + getPriceGivenYL(endReserves.ry, endReserves.L, params); endReserves.rx = getNextReserveX( - poolId, endReserves.ry, endReserves.L, approxPrice + endReserves.ry, endReserves.L, approxPrice, params ); require( @@ -325,28 +325,24 @@ contract SYCoveredCallSolver { return ( valid, state.amountOut, - computePriceGivenX(endReserves.rx, endReserves.L, poolParams), + computePriceGivenX(endReserves.rx, endReserves.L, params), swapData ); } function getPriceGivenYL( - uint256 poolId, uint256 ry, - uint256 L - ) public view returns (uint256 price) { - SYCoveredCallParams memory params = - getPoolParamsCustomTimestamp(poolId, block.timestamp); + uint256 L, + SYCoveredCallParams memory params + ) public pure returns (uint256 price) { price = computePriceGivenY(ry, L, params); } function getPriceGivenXL( - uint256 poolId, uint256 rx, - uint256 L - ) public view returns (uint256 price) { - SYCoveredCallParams memory params = - getPoolParamsCustomTimestamp(poolId, block.timestamp); + uint256 L, + SYCoveredCallParams memory params + ) public pure returns (uint256 price) { price = computePriceGivenX(rx, L, params); } diff --git a/test/SYCoveredCall/unit/SetUp.sol b/test/SYCoveredCall/unit/SetUp.sol index 7a6c8116..78ef5965 100644 --- a/test/SYCoveredCall/unit/SetUp.sol +++ b/test/SYCoveredCall/unit/SetUp.sol @@ -11,8 +11,10 @@ import "forge-std/console2.sol"; import { IPMarket } from "pendle/interfaces/IPMarket.sol"; import "pendle/core/Market/MarketMathCore.sol"; import "pendle/interfaces/IPAllActionV3.sol"; +import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; contract SYCoveredCallSetUp is SetUp { + using FixedPointMathLib for uint256; using MarketMathCore for MarketState; using MarketMathCore for int256; using MarketMathCore for uint256; @@ -42,6 +44,8 @@ contract SYCoveredCallSetUp is SetUp { MarketState public pendleMarketState; int256 pendleRateAnchor; int256 pendleRateScalar; + int256 pendleExchangeRate; + uint256 ptRate; uint256 timeToExpiry; function mintSY(uint256 amount) public { @@ -58,6 +62,7 @@ contract SYCoveredCallSetUp is SetUp { vm.createSelectFork({ urlOrAlias: "mainnet", blockNumber: 19_662_269 }); SetUp.setUp(); (SY, PT, YT) = IPMarket(market).readTokens(); + ptRate = SY.exchangeRate(); pendleMarketState = market.readState(address(router)); coveredCall = new SYCoveredCall(address(dfmm)); solver = new SYCoveredCallSolver(address(coveredCall)); @@ -93,19 +98,22 @@ contract SYCoveredCallSetUp is SetUp { tokens[0] = address(tokenX); tokens[1] = address(tokenY); + pendleExchangeRate = pendleMarketState.totalPt._getExchangeRate(pendleMarketState.totalSy, pendleRateScalar, pendleRateAnchor, 0); + defaultPrice = ptRate.mulWadDown(uint256(pendleExchangeRate)); + SYCoveredCallParams memory defaultParams = SYCoveredCallParams({ - mean: uint256(pendleRateAnchor), + mean: defaultPrice, width: 0.1 ether, maturity: PT.expiry(), swapFee: 0.0005 ether, lastTimestamp: block.timestamp, + lastImpliedPrice: 0, controller: address(this), SY: SY, PT: PT, YT: YT }); - defaultPrice = uint256(pendleRateAnchor); bytes memory initialPoolData = solver.getInitialPoolDataGivenX( defaultReserveX, defaultPrice, defaultParams diff --git a/test/SYCoveredCall/unit/Swap.t.sol b/test/SYCoveredCall/unit/Swap.t.sol index 5ac5f643..7b58094b 100644 --- a/test/SYCoveredCall/unit/Swap.t.sol +++ b/test/SYCoveredCall/unit/Swap.t.sol @@ -49,7 +49,7 @@ contract SYCoveredCallSwapTest is SYCoveredCallSetUp { (bool valid, uint256 amountOut,, bytes memory payload) = solver.simulateSwap(POOL_ID, swapXForY, amountIn, block.timestamp); assertEq(valid, true); - + console2.log("swapIsValid", valid); console2.log("out", amountOut); (,, uint256 inputAmount, uint256 outputAmount) = @@ -131,16 +131,16 @@ contract SYCoveredCallSwapTest is SYCoveredCallSetUp { SYCoveredCallParams memory poolParams = solver.getPoolParams(POOL_ID); uint256 startL = solver.getNextLiquidity( - POOL_ID, preReserves[0], preReserves[1], preTotalLiquidity + preReserves[0], preReserves[1], preTotalLiquidity, poolParams ); uint256 deltaLiquidity = amountIn.mulWadUp(poolParams.swapFee).divWadUp(poolParams.mean); uint256 ry = preReserves[1] + amountIn; uint256 L = startL + deltaLiquidity; - uint256 approxPrice = solver.getPriceGivenYL(POOL_ID, ry, L); + uint256 approxPrice = solver.getPriceGivenYL(ry, L, poolParams); - uint256 rx = solver.getNextReserveX(POOL_ID, ry, L, approxPrice); + uint256 rx = solver.getNextReserveX(ry, L, approxPrice, poolParams); int256 invariant = computeTradingFunction(rx, ry, L, poolParams); while (invariant >= 0) {