From 95b692876491600d17f508a119cde28ec2d3e1d9 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 18 Mar 2024 12:31:56 -0400 Subject: [PATCH 001/119] test for LP token balance delta on allocate --- test/G3M/unit/Allocate.t.sol | 28 ++++++++++++++++++++++++++-- test/G3M/unit/SetUp.sol | 24 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/test/G3M/unit/Allocate.t.sol b/test/G3M/unit/Allocate.t.sol index a2a4ef2c..9851f0d7 100644 --- a/test/G3M/unit/Allocate.t.sol +++ b/test/G3M/unit/Allocate.t.sol @@ -107,8 +107,10 @@ contract G3MAllocateTest is G3MSetUp { solver.allocateGivenDeltaY(POOL_ID, maxDeltaY); (uint256[] memory reserves, uint256 liquidity) = getReservesAndLiquidity(POOL_ID); + console2.log("liquidity", liquidity); uint256 preLiquidityBalance = liquidityOf(address(this), POOL_ID); + console2.log(preLiquidityBalance); bytes memory data = abi.encode(maxDeltaX, maxDeltaY, deltaLiquidity); console2.log(maxDeltaX); @@ -124,11 +126,33 @@ contract G3MAllocateTest is G3MSetUp { assertEq(adjustedReserves[1], reserves[1] + deltas[1]); assertEq(adjustedLiquidity, liquidity + deltaLiquidity); - /* assertEq( preLiquidityBalance + deltaLiquidity, liquidityOf(address(this), POOL_ID) ); - */ + } + + function test_G3M_allocate_ReceiveAppropriateLpTokens() public init_100 { + + (uint256[] memory initialReserves, uint256 initialL) = getReservesAndLiquidity(POOL_ID); + + console2.log("Initial Liquidity", initialL); + + uint256 dyMax = 100 ether; + (uint256 dxMax, uint256 dL) = solver.allocateGivenDeltaY(POOL_ID, dyMax); + + uint256 preLpTokens = liquidityOf(address(this), POOL_ID); + + bytes memory data = abi.encode(dxMax, dyMax, dL); + + (uint256[] memory deltas) = dfmm.allocate(POOL_ID, data); + + (uint256[] memory nextReserves, uint256 nextL) = getReservesAndLiquidity(POOL_ID); + uint256 postLpTokens = liquidityOf(address(this), POOL_ID); + + console2.log("preLp", preLpTokens); + console2.log("postLp", postLpTokens); + + assertApproxEqAbs(preLpTokens + dL, postLpTokens, 1000); } } diff --git a/test/G3M/unit/SetUp.sol b/test/G3M/unit/SetUp.sol index 1cd97be1..24069a51 100644 --- a/test/G3M/unit/SetUp.sol +++ b/test/G3M/unit/SetUp.sol @@ -31,6 +31,10 @@ contract G3MSetUp is SetUp { defaultReserveX, defaultStrikePrice, defaultParams ); + bytes default100InitialPoolData = computeInitialPoolData( + defaultReserveX * 100, defaultStrikePrice, defaultParams + ); + function setUp() public override { SetUp.setUp(); g3m = new GeometricMean(address(dfmm)); @@ -58,4 +62,24 @@ contract G3MSetUp is SetUp { _; } + + modifier init_100() { + address[] memory tokens = new address[](2); + tokens[0] = address(tokenX); + tokens[1] = address(tokenY); + + InitParams memory defaultInitParams = InitParams({ + name: "", + symbol: "", + strategy: address(g3m), + tokens: tokens, + data: default100InitialPoolData, + feeCollector: address(0), + controllerFee: 0 + }); + + (POOL_ID,,) = dfmm.init(defaultInitParams); + + _; + } } From 8cff3fc93faebd503bd56348830ddea6b5d880a0 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 18 Mar 2024 14:04:27 -0400 Subject: [PATCH 002/119] reproduce audit finding --- src/DFMM.sol | 1 + test/G3M/unit/Allocate.t.sol | 19 ++++++++----------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index ea727b3e..d55462cf 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -161,6 +161,7 @@ contract DFMM is IDFMM { _pools[poolId].reserves[i] += deltas[i]; } + _pools[poolId].totalLiquidity += deltaLiquidity; _manageTokens(msg.sender, poolId, true, deltaLiquidity); diff --git a/test/G3M/unit/Allocate.t.sol b/test/G3M/unit/Allocate.t.sol index 9851f0d7..8700c82a 100644 --- a/test/G3M/unit/Allocate.t.sol +++ b/test/G3M/unit/Allocate.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import "./SetUp.sol"; import "solmate/utils/FixedPointMathLib.sol"; +import { LPToken } from "src/LPToken.sol"; contract G3MAllocateTest is G3MSetUp { using FixedPointMathLib for uint256; @@ -133,26 +134,22 @@ contract G3MAllocateTest is G3MSetUp { } function test_G3M_allocate_ReceiveAppropriateLpTokens() public init_100 { - (uint256[] memory initialReserves, uint256 initialL) = getReservesAndLiquidity(POOL_ID); + Pool memory pool = dfmm.pools(POOL_ID); + LPToken liquidityToken = LPToken(pool.liquidityToken); - console2.log("Initial Liquidity", initialL); + uint256 startBalance = liquidityToken.balanceOf(address(this)); uint256 dyMax = 100 ether; (uint256 dxMax, uint256 dL) = solver.allocateGivenDeltaY(POOL_ID, dyMax); - - uint256 preLpTokens = liquidityOf(address(this), POOL_ID); - bytes memory data = abi.encode(dxMax, dyMax, dL); - (uint256[] memory deltas) = dfmm.allocate(POOL_ID, data); + dfmm.allocate(POOL_ID, data); (uint256[] memory nextReserves, uint256 nextL) = getReservesAndLiquidity(POOL_ID); - uint256 postLpTokens = liquidityOf(address(this), POOL_ID); - - console2.log("preLp", preLpTokens); - console2.log("postLp", postLpTokens); + uint256 endBalance = liquidityToken.balanceOf(address(this)); - assertApproxEqAbs(preLpTokens + dL, postLpTokens, 1000); + console2.log("startBalance", startBalance); + console2.log("endBalance", endBalance); } } From 8c675ccff8962b6a2bcee65b381ed3c90b44a02d Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 18 Mar 2024 14:06:19 -0400 Subject: [PATCH 003/119] fix liquidity management order of ops --- src/DFMM.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index d55462cf..1c3851b2 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -161,9 +161,8 @@ contract DFMM is IDFMM { _pools[poolId].reserves[i] += deltas[i]; } - - _pools[poolId].totalLiquidity += deltaLiquidity; _manageTokens(msg.sender, poolId, true, deltaLiquidity); + _pools[poolId].totalLiquidity += deltaLiquidity; for (uint256 i = 0; i < length; i++) { _transferFrom(_pools[poolId].tokens[i], deltas[i]); From 0557365106c4cebad9bb257b7720eb0233cc73ce Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 18 Mar 2024 14:11:12 -0400 Subject: [PATCH 004/119] fix lptoken allocate test assertions --- test/G3M/unit/Allocate.t.sol | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/G3M/unit/Allocate.t.sol b/test/G3M/unit/Allocate.t.sol index 8700c82a..c671ebf9 100644 --- a/test/G3M/unit/Allocate.t.sol +++ b/test/G3M/unit/Allocate.t.sol @@ -134,7 +134,7 @@ contract G3MAllocateTest is G3MSetUp { } function test_G3M_allocate_ReceiveAppropriateLpTokens() public init_100 { - (uint256[] memory initialReserves, uint256 initialL) = getReservesAndLiquidity(POOL_ID); + (, uint256 initialL) = getReservesAndLiquidity(POOL_ID); Pool memory pool = dfmm.pools(POOL_ID); LPToken liquidityToken = LPToken(pool.liquidityToken); @@ -146,10 +146,11 @@ contract G3MAllocateTest is G3MSetUp { dfmm.allocate(POOL_ID, data); - (uint256[] memory nextReserves, uint256 nextL) = getReservesAndLiquidity(POOL_ID); + (, uint256 nextL) = getReservesAndLiquidity(POOL_ID); uint256 endBalance = liquidityToken.balanceOf(address(this)); - - console2.log("startBalance", startBalance); - console2.log("endBalance", endBalance); + + // Add 1_000 wei to account for liquidity that was burnt on init + assertEq(startBalance + 1_000, initialL); + assertEq(endBalance + 1_000, nextL); } } From 27381eddfad873d23cefc14e93a5538c5be36364 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 10:41:17 +0400 Subject: [PATCH 005/119] fix: optimize duplicate tokens loop check --- src/DFMM.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index ea727b3e..d496a2c9 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -109,8 +109,8 @@ contract DFMM is IDFMM { for (uint256 i = 0; i < tokensLength; i++) { address token = params.tokens[i]; - for (uint256 j = 0; j < tokensLength; j++) { - if (i != j && token == params.tokens[j]) { + for (uint256 j = i + 1; j < tokensLength; j++) { + if (token == params.tokens[j]) { revert InvalidDuplicateTokens(); } } From 80407890eb22f61f54e7b69fe1af71a1771510af Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 11:25:00 +0400 Subject: [PATCH 006/119] fix: DFMM init now returns the totalLiquidity of the pool --- src/DFMM.sol | 2 +- test/DFMM/unit/Init.t.sol | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index ea727b3e..7251bb4e 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -136,7 +136,7 @@ contract DFMM is IDFMM { pool.totalLiquidity ); - return (poolId, reserves, totalLiquidity - BURNT_LIQUIDITY); + return (poolId, reserves, totalLiquidity); } /// @inheritdoc IDFMM diff --git a/test/DFMM/unit/Init.t.sol b/test/DFMM/unit/Init.t.sol index 07625d3f..695d967d 100644 --- a/test/DFMM/unit/Init.t.sol +++ b/test/DFMM/unit/Init.t.sol @@ -31,8 +31,7 @@ contract DFMMInit is DFMMSetUp, Script { function test_DFMM_init_ReturnsStrategyInitialReserves() public { (, uint256[] memory reserves, uint256 totalLiquidity) = dfmm.init(getDefaultPoolParams(defaultData)); - // A bit of the liquidity is burnt - assertEq(initialLiquidity - 1000, totalLiquidity); + assertEq(initialLiquidity, totalLiquidity); assertEq(initialReserveX, reserves[0]); assertEq(initialReserveY, reserves[1]); } From d30b822436d2eacaadb7a66490a82bd6b34aa05a Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 12:13:03 +0400 Subject: [PATCH 007/119] fix: check if token is WETH in _transferFrom --- src/DFMM.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index ea727b3e..9b37ae43 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -285,7 +285,7 @@ contract DFMM is IDFMM { * @param amount Amount to transfer expressed in WAD. */ function _transferFrom(address token, uint256 amount) internal { - if (address(this).balance >= amount) { + if (token == weth && address(this).balance >= amount) { WETH(payable(weth)).deposit{ value: amount }(); if (address(this).balance > 0) { From 43b769a4d28af714775c39f301380c377c656399 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 12:17:05 +0400 Subject: [PATCH 008/119] test: use WETH address in _transferFrom tests --- test/DFMM/unit/Internal.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/DFMM/unit/Internal.t.sol b/test/DFMM/unit/Internal.t.sol index 5c35b676..96b9807e 100644 --- a/test/DFMM/unit/Internal.t.sol +++ b/test/DFMM/unit/Internal.t.sol @@ -29,7 +29,7 @@ contract DFMMInternalTest is DFMMSetUp { function test_DFMM_transferFrom_WrapsETH() public { uint256 amount = 1 ether; - dfmmInternal.transferFrom{ value: amount }(address(0), amount); + dfmmInternal.transferFrom{ value: amount }(address(weth), amount); assertEq(weth.balanceOf(address(dfmmInternal)), amount); assertEq(address(weth).balance, 1 ether); assertEq(address(dfmmInternal).balance, 0); @@ -37,7 +37,7 @@ contract DFMMInternalTest is DFMMSetUp { function test_DFMM_transferFrom_RefundsExtraETH() public { uint256 amount = 1 ether; - dfmmInternal.transferFrom{ value: amount * 2 }(address(0), amount); + dfmmInternal.transferFrom{ value: amount * 2 }(address(weth), amount); assertEq(weth.balanceOf(address(dfmmInternal)), amount); assertEq(address(weth).balance, 1 ether); assertEq(address(dfmmInternal).balance, 0); From e1c4eea5b49ef3da4ef01c749ac9d4bb68a69c48 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 12:52:04 +0400 Subject: [PATCH 009/119] feat: avoid stuck ETH in _transferFrom --- src/DFMM.sol | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index 9b37ae43..2147fe04 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -285,14 +285,16 @@ contract DFMM is IDFMM { * @param amount Amount to transfer expressed in WAD. */ function _transferFrom(address token, uint256 amount) internal { - if (token == weth && address(this).balance >= amount) { - WETH(payable(weth)).deposit{ value: amount }(); - - if (address(this).balance > 0) { - SafeTransferLib.safeTransferETH( - msg.sender, address(this).balance + if (token == weth) { + if (address(this).balance >= amount) { + WETH(payable(weth)).deposit{ value: amount }(); + } else { + SafeTransferLib.safeTransferFrom( + ERC20(token), msg.sender, address(this), amount ); } + + SafeTransferLib.safeTransferETH(msg.sender, address(this).balance); } else { uint256 downscaledAmount = downscaleUp(amount, computeScalingFactor(token)); From 849cac0b41cf7366125def4e5e672b61b46a4b8e Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 12:52:24 +0400 Subject: [PATCH 010/119] test: add test_DFMM_transferFrom_UsesWETH --- test/DFMM/unit/Internal.t.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/DFMM/unit/Internal.t.sol b/test/DFMM/unit/Internal.t.sol index 96b9807e..56a2a01a 100644 --- a/test/DFMM/unit/Internal.t.sol +++ b/test/DFMM/unit/Internal.t.sol @@ -43,6 +43,16 @@ contract DFMMInternalTest is DFMMSetUp { assertEq(address(dfmmInternal).balance, 0); } + function test_DFMM_transferFrom_UsesWETH() public { + uint256 amount = 1 ether; + weth.deposit{ value: amount }(); + weth.approve(address(dfmmInternal), amount); + dfmmInternal.transferFrom(address(weth), amount); + assertEq(weth.balanceOf(address(dfmmInternal)), amount); + assertEq(address(weth).balance, amount); + assertEq(address(dfmmInternal).balance, 0); + } + function testFuzz_DFMM_transferFrom_TransferTokens(uint256 amount) public { vm.assume( amount From 6594c38ef0d728024f19bbd2757f9f93c4929887 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 12:52:38 +0400 Subject: [PATCH 011/119] test: add test_DFMM_transferFrom_UsesWETHAndRefunds --- test/DFMM/unit/Internal.t.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/DFMM/unit/Internal.t.sol b/test/DFMM/unit/Internal.t.sol index 56a2a01a..db77e321 100644 --- a/test/DFMM/unit/Internal.t.sol +++ b/test/DFMM/unit/Internal.t.sol @@ -53,6 +53,16 @@ contract DFMMInternalTest is DFMMSetUp { assertEq(address(dfmmInternal).balance, 0); } + function test_DFMM_transferFrom_UsesWETHAndRefunds() public { + uint256 amount = 1 ether; + weth.deposit{ value: amount }(); + weth.approve(address(dfmmInternal), amount); + dfmmInternal.transferFrom{ value: amount - 1 }(address(weth), amount); + assertEq(weth.balanceOf(address(dfmmInternal)), amount); + assertEq(address(weth).balance, amount); + assertEq(address(dfmmInternal).balance, 0); + } + function testFuzz_DFMM_transferFrom_TransferTokens(uint256 amount) public { vm.assume( amount From c62ca0eeeb6dfd01476d21eed17e11fe44a57b5e Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 12:57:29 +0400 Subject: [PATCH 012/119] feat: add balance zero check before refunding ETH in _transferFrom --- src/DFMM.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index 2147fe04..5fae79c7 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -294,7 +294,11 @@ contract DFMM is IDFMM { ); } - SafeTransferLib.safeTransferETH(msg.sender, address(this).balance); + if (address(this).balance > 0) { + SafeTransferLib.safeTransferETH( + msg.sender, address(this).balance + ); + } } else { uint256 downscaledAmount = downscaleUp(amount, computeScalingFactor(token)); From fe89db10054e5f08764b53e2e59fbb5694bb0bea Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 13:09:15 +0400 Subject: [PATCH 013/119] chore: add commit to OZ clone function link --- src/DFMM.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index ea727b3e..49d41a6b 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -362,7 +362,7 @@ contract DFMM is IDFMM { * @dev Deploys and returns the address of a clone contract that mimics * the behaviour of the contract deployed at the address `implementation`. * This function uses the `CREATE` opcode, which should never revert. - * This function was taken from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Clones.sol#L23. + * This function was taken from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7bd2b2aaf68c21277097166a9a51eb72ae239b34/contracts/proxy/Clones.sol#L23-L41. */ function clone(address implementation) internal From bf1102c20ac5f54981179b205d52f051f9ed2667 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 13:25:37 +0400 Subject: [PATCH 014/119] feat: add InvalidReserves error --- src/interfaces/IDFMM.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/interfaces/IDFMM.sol b/src/interfaces/IDFMM.sol index 1adab2cf..6b61696a 100644 --- a/src/interfaces/IDFMM.sol +++ b/src/interfaces/IDFMM.sol @@ -56,6 +56,9 @@ interface IDFMM { /// @dev Thrown when pool tokens are identical. error InvalidDuplicateTokens(); + /// @dev Thrown when a strategy is returning an invalid amount of reserves. + error InvalidReserves(); + /// @dev Thrown when a pool is being initalized with less than two tokens. error InvalidMinimumTokens(); From ed37a9bc544dedb772076fb6d5ca4fd048e000a1 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 13:26:31 +0400 Subject: [PATCH 015/119] fix: compare reserves and tokens length in init --- src/DFMM.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/DFMM.sol b/src/DFMM.sol index ea727b3e..c84604ef 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -92,6 +92,8 @@ contract DFMM is IDFMM { msg.sender, _pools.length, pool, params.data ); + if (reserves.length != params.tokens.length) revert InvalidReserves(); + if (!valid) revert InvalidInvariant(invariant); liquidityToken.initialize(params.name, params.symbol); From 1fe6809fea3a2804554c5ebeea50c9884705cc18 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 13:34:44 +0400 Subject: [PATCH 016/119] fix: update controller fees minting in swap --- src/DFMM.sol | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index ea727b3e..17ee7e2f 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -237,13 +237,14 @@ contract DFMM is IDFMM { if (!state.valid) revert InvalidInvariant(state.invariant); - _pools[poolId].totalLiquidity += state.deltaLiquidity; - if (_pools[poolId].feeCollector != address(0)) { - uint256 fees = state.deltaLiquidity * _pools[poolId].controllerFee - / FixedPointMathLib.WAD; - if (fees == 0) ++fees; + uint256 fees = + state.deltaLiquidity.mulWadDown(_pools[poolId].controllerFee); + _pools[poolId].totalLiquidity += state.deltaLiquidity - fees; _manageTokens(_pools[poolId].feeCollector, poolId, true, fees); + _pools[poolId].totalLiquidity += fees; + } else { + _pools[poolId].totalLiquidity += state.deltaLiquidity; } _pools[poolId].reserves[state.tokenInIndex] += state.amountIn; From 6d3eeb934147a415cc3dcf19ae1a51f88afb31df Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 13:41:14 +0400 Subject: [PATCH 017/119] test: update test_DFMM_swap_MintsLPTokensToFeeCollector --- test/DFMM/unit/Swap.t.sol | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/DFMM/unit/Swap.t.sol b/test/DFMM/unit/Swap.t.sol index 15732e66..0924c941 100644 --- a/test/DFMM/unit/Swap.t.sol +++ b/test/DFMM/unit/Swap.t.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; +import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; import { LPToken, Pool } from "src/DFMM.sol"; import { DFMMSetUp } from "./SetUp.sol"; contract DFMMSwapTest is DFMMSetUp { + using FixedPointMathLib for uint256; + function test_DFMM_swap_IncreasesTotalLiquidity() public { skip(); } @@ -43,9 +46,9 @@ contract DFMMSwapTest is DFMMSetUp { uint256 preBalance = token.balanceOf(address(this)); uint256 deltaLiquidity = 1 ether; - uint256 fees = deltaLiquidity * pool.controllerFee / 1 ether; - uint256 feesInToken = - fees * token.totalSupply() / (pool.totalLiquidity + deltaLiquidity); + uint256 fees = deltaLiquidity.mulWadDown(pool.controllerFee); + uint256 feesInToken = fees * token.totalSupply() + / (pool.totalLiquidity + deltaLiquidity - fees); dfmm.swap( POOL_ID, address(this), From 051f786ae186b18baa92d8c17bdfe5ea262b9172 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 13:55:33 +0400 Subject: [PATCH 018/119] fix: typo in _computeDeallocateDeltasGivenDeltaL NatSpec --- src/PairStrategy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PairStrategy.sol b/src/PairStrategy.sol index 5dcff55b..18f8e258 100644 --- a/src/PairStrategy.sol +++ b/src/PairStrategy.sol @@ -182,7 +182,7 @@ abstract contract PairStrategy is IStrategy { ) internal view virtual returns (uint256[] memory); /** - * @dev Computes the deltas to de de allocate given a liquidity. + * @dev Computes the deltas to deallocate given a liquidity. * delta. This function is meant to be implemented by the * strategy inheriting from this contract. * @param deltaLiquidity Amount of liquidity to deallocate. From cfd7ddc377d40a278e3a6d72971f42e64d426656 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 14:00:06 +0400 Subject: [PATCH 019/119] fix: add missing NatSpec to Pool and InitParams structs --- src/interfaces/IDFMM.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/interfaces/IDFMM.sol b/src/interfaces/IDFMM.sol index 1adab2cf..7e089674 100644 --- a/src/interfaces/IDFMM.sol +++ b/src/interfaces/IDFMM.sol @@ -8,6 +8,8 @@ pragma solidity ^0.8.13; * @param reserves Array of token reserves in the pool in WAD. * @param totalLiquidity Total liquidity in the pool. * @param liquidityToken Address of the LP token contract. + * @param feeCollector Address receiving controller fees. + * @param controllerFee Fees charged on the swap fee (in WAD). */ struct Pool { address strategy; @@ -26,6 +28,8 @@ struct Pool { * @param strategy Address of the associated strategy contract. * @param tokens Array of token addresses in the pool. * @param data An array of bytes used by the strategy contract. + * @param feeCollector Address receiving controller fees. + * @param controllerFee Fees charged on the swap fee (in WAD). */ struct InitParams { string name; From 163d7d300f6358e9c8983045915b000ba521159f Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 15:15:05 +0400 Subject: [PATCH 020/119] fix: return controller in ConstantSum getPoolParams --- src/ConstantSum/ConstantSum.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index 08a1c27d..5e9a44a9 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -105,6 +105,7 @@ contract ConstantSum is PairStrategy { params.price = internalParams[poolId].price; params.swapFee = internalParams[poolId].swapFee; + params.controller = internalParams[poolId].controller; return abi.encode(params); } From 21031b5e4bc0ba0324a28fbfff0db57526d1ac46 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 15:15:29 +0400 Subject: [PATCH 021/119] test: add test_ConstantSum_getPoolParams --- test/ConstantSum/unit/GetPoolParams.t.sol | 41 +++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/test/ConstantSum/unit/GetPoolParams.t.sol b/test/ConstantSum/unit/GetPoolParams.t.sol index 8381be67..742b7c05 100644 --- a/test/ConstantSum/unit/GetPoolParams.t.sol +++ b/test/ConstantSum/unit/GetPoolParams.t.sol @@ -1,6 +1,43 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import { ConstantSumSetUp } from "./SetUp.sol"; +import { ConstantSumSetUp, ConstantSumParams, InitParams } from "./SetUp.sol"; -contract ConstantSumGetPoolParamsTest is ConstantSumSetUp { } +contract ConstantSumGetPoolParamsTest is ConstantSumSetUp { + function test_ConstantSum_getPoolParams() public { + ConstantSumParams memory initialParams = ConstantSumParams({ + price: 1 ether, + swapFee: TEST_SWAP_FEE, + controller: address(this) + }); + + uint256 reserveX = 1 ether; + uint256 reserveY = 1 ether; + + bytes memory initData = + solver.getInitialPoolData(reserveX, reserveY, initialParams); + + address[] memory tokens = new address[](2); + tokens[0] = address(tokenX); + tokens[1] = address(tokenY); + + InitParams memory initParams = InitParams({ + name: "", + symbol: "", + strategy: address(constantSum), + tokens: tokens, + data: initData, + feeCollector: address(0), + controllerFee: 0 + }); + + dfmm.init(initParams); + + ConstantSumParams memory poolParams = + abi.decode(constantSum.getPoolParams(0), (ConstantSumParams)); + + assertEq(poolParams.price, initialParams.price); + assertEq(poolParams.swapFee, initialParams.swapFee); + assertEq(poolParams.controller, initialParams.controller); + } +} From fa84ff188a4fb789db3fce9313806a5c32ed4aa3 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 18:40:50 +0400 Subject: [PATCH 022/119] test: update ConstantSum getPoolParams test --- test/ConstantSum/unit/GetPoolParams.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ConstantSum/unit/GetPoolParams.t.sol b/test/ConstantSum/unit/GetPoolParams.t.sol index 742b7c05..67f74d09 100644 --- a/test/ConstantSum/unit/GetPoolParams.t.sol +++ b/test/ConstantSum/unit/GetPoolParams.t.sol @@ -31,10 +31,10 @@ contract ConstantSumGetPoolParamsTest is ConstantSumSetUp { controllerFee: 0 }); - dfmm.init(initParams); + (uint256 poolId,,) = dfmm.init(initParams); ConstantSumParams memory poolParams = - abi.decode(constantSum.getPoolParams(0), (ConstantSumParams)); + abi.decode(constantSum.getPoolParams(poolId), (ConstantSumParams)); assertEq(poolParams.price, initialParams.price); assertEq(poolParams.swapFee, initialParams.swapFee); From 88b596be688d73593e56ced76e07832e432077fc Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 19 Mar 2024 18:41:10 +0400 Subject: [PATCH 023/119] fix: add missing controller param in ConstantSum init --- src/ConstantSum/ConstantSum.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index 5e9a44a9..aaa2d170 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -63,6 +63,7 @@ contract ConstantSum is PairStrategy { internalParams[poolId].price = params.price; internalParams[poolId].swapFee = params.swapFee; + internalParams[poolId].controller = params.controller; // Get the trading function and check this is valid invariant = From 982765a778c38b401473244699d079addb520d81 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 20 Mar 2024 19:23:46 +0400 Subject: [PATCH 024/119] fix: ConstantSum computeTradingFunction, computeInitialPoolData --- src/ConstantSum/ConstantSumMath.sol | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/ConstantSum/ConstantSumMath.sol b/src/ConstantSum/ConstantSumMath.sol index 5e58f9e2..f7e5d9ff 100644 --- a/src/ConstantSum/ConstantSumMath.sol +++ b/src/ConstantSum/ConstantSumMath.sol @@ -13,8 +13,10 @@ function computeTradingFunction( uint256 totalLiquidity, uint256 price ) pure returns (int256) { - return int256(reserves[0].divWadUp(totalLiquidity)) - + int256(reserves[1].divWadUp(totalLiquidity.mulWadUp(price))) - int256(ONE); + return int256( + price.mulWadUp(reserves[0].divWadUp(totalLiquidity)) + + reserves[1].divWadUp(totalLiquidity) + ) - int256(ONE); } function computeInitialPoolData( @@ -24,13 +26,21 @@ function computeInitialPoolData( ) pure returns (bytes memory) { // The pool can be initialized with any non-negative amount of rx, and ry. // so we have to allow a user to pass an amount of both even if one is zero. - uint256 L = rx + ry.divWadUp(params.price); + uint256 L = rx.mulWadDown(params.price) + ry; uint256[] memory reserves = new uint256[](2); reserves[0] = rx; reserves[1] = ry; return abi.encode(reserves, L, params); } +function computeDeltaLiquidity( + uint256 deltaX, + uint256 deltaY, + uint256 price +) pure returns (uint256) { + return price.mulWadUp(deltaX) + deltaY; +} + function computeDeallocateGivenDeltaX( uint256 deltaX, uint256 rX, From 746cbc1283cc49a3b3f57c4451ca40cb7c1d292a Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 20 Mar 2024 19:25:14 +0400 Subject: [PATCH 025/119] fix: compute deltaLiquidity in validateAllocate in ConstantSum --- src/ConstantSum/ConstantSum.sol | 50 +++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index 08a1c27d..81811702 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -2,7 +2,9 @@ pragma solidity 0.8.22; import { - FixedPointMathLib, computeTradingFunction + FixedPointMathLib, + computeTradingFunction, + computeDeltaLiquidity } from "./ConstantSumMath.sol"; import { decodePriceUpdate, @@ -33,6 +35,9 @@ enum UpdateCode { contract ConstantSum is PairStrategy { using FixedPointMathLib for uint256; + /// @notice Thrown when the expected liquidity is not met. + error InvalidDeltaLiquidity(); + /// @inheritdoc IStrategy string public constant name = "ConstantSum"; @@ -73,6 +78,45 @@ contract ConstantSum is PairStrategy { return (valid, invariant, reserves, totalLiquidity); } + function validateAllocate( + address, + uint256 poolId, + Pool memory pool, + bytes calldata data + ) + external + view + override + returns ( + bool valid, + int256 invariant, + uint256[] memory deltas, + uint256 deltaLiquidity + ) + { + (uint256 deltaX, uint256 deltaY, uint256 minDeltaL) = + abi.decode(data, (uint256, uint256, uint256)); + + deltaLiquidity = + computeDeltaLiquidity(deltaX, deltaY, internalParams[poolId].price); + if (deltaLiquidity < minDeltaL) revert InvalidDeltaLiquidity(); + + deltas = new uint256[](2); + deltas[0] = deltaX; + deltas[1] = deltaY; + + pool.reserves[0] += deltaX; + pool.reserves[1] += deltaY; + + invariant = tradingFunction( + pool.reserves, + pool.totalLiquidity + deltaLiquidity, + getPoolParams(poolId) + ); + + valid = invariant >= 0; + } + /// @inheritdoc IStrategy function update( address sender, @@ -128,7 +172,7 @@ contract ConstantSum is PairStrategy { Pool memory, bytes memory ) internal pure override returns (uint256[] memory) { - return new uint256[](0); + return new uint256[](2); } /// @inheritdoc PairStrategy @@ -137,6 +181,6 @@ contract ConstantSum is PairStrategy { Pool memory, bytes memory ) internal pure override returns (uint256[] memory) { - return new uint256[](0); + return new uint256[](2); } } From 3f2bd90fc883ab379fb2818ea1fedf07b57ce28f Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 20 Mar 2024 19:43:35 +0400 Subject: [PATCH 026/119] fix: override validateDeallocate in ConstantSum --- src/ConstantSum/ConstantSum.sol | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index 81811702..b8032da5 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -117,6 +117,45 @@ contract ConstantSum is PairStrategy { valid = invariant >= 0; } + function validateDeallocate( + address, + uint256 poolId, + Pool memory pool, + bytes calldata data + ) + external + view + override + returns ( + bool valid, + int256 invariant, + uint256[] memory deltas, + uint256 deltaLiquidity + ) + { + (uint256 deltaX, uint256 deltaY, uint256 maxDeltaL) = + abi.decode(data, (uint256, uint256, uint256)); + + deltaLiquidity = + computeDeltaLiquidity(deltaX, deltaY, internalParams[poolId].price); + if (maxDeltaL > deltaLiquidity) revert InvalidDeltaLiquidity(); + + deltas = new uint256[](2); + deltas[0] = deltaX; + deltas[1] = deltaY; + + pool.reserves[0] -= deltaX; + pool.reserves[1] -= deltaY; + + invariant = tradingFunction( + pool.reserves, + pool.totalLiquidity - deltaLiquidity, + getPoolParams(poolId) + ); + + valid = invariant >= 0; + } + /// @inheritdoc IStrategy function update( address sender, From f3698b1a9bc580ac97896006f47ff1f775d552e6 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 20 Mar 2024 19:44:00 +0400 Subject: [PATCH 027/119] fix: remove incorrect functions in ConstantSumMath --- src/ConstantSum/ConstantSumMath.sol | 68 ----------------------------- 1 file changed, 68 deletions(-) diff --git a/src/ConstantSum/ConstantSumMath.sol b/src/ConstantSum/ConstantSumMath.sol index f7e5d9ff..94dc3aeb 100644 --- a/src/ConstantSum/ConstantSumMath.sol +++ b/src/ConstantSum/ConstantSumMath.sol @@ -40,71 +40,3 @@ function computeDeltaLiquidity( ) pure returns (uint256) { return price.mulWadUp(deltaX) + deltaY; } - -function computeDeallocateGivenDeltaX( - uint256 deltaX, - uint256 rX, - uint256 rY, - uint256 totalLiquidity -) pure returns (uint256 deltaY, uint256 deltaL) { - uint256 a = deltaX.divWadDown(rX); - if (rY > 0) { - deltaY = a.mulWadDown(rY); - } - deltaL = a.mulWadDown(totalLiquidity); -} - -function computeDeallocateGivenDeltaY( - uint256 deltaY, - uint256 rX, - uint256 rY, - uint256 totalLiquidity -) pure returns (uint256 deltaX, uint256 deltaL) { - uint256 a = deltaY.divWadDown(rY); - if (rX > 0) { - deltaX = a.mulWadDown(rX); - } - deltaL = a.mulWadDown(totalLiquidity); -} - -function computeAllocateGivenDeltaX( - uint256 deltaX, - uint256 rX, - uint256 rY, - uint256 totalLiquidity -) pure returns (uint256 deltaY, uint256 deltaL) { - uint256 a = deltaX.divWadUp(rX); - if (rY > 0) { - deltaY = a.mulWadUp(rY); - } - deltaL = a.mulWadUp(totalLiquidity); -} - -function computeAllocateGivenDeltaY( - uint256 deltaY, - uint256 rX, - uint256 rY, - uint256 totalLiquidity -) pure returns (uint256 deltaX, uint256 deltaL) { - uint256 a = deltaY.divWadUp(rY); - if (rX > 0) { - deltaX = a.mulWadUp(rX); - } - deltaL = a.mulWadUp(totalLiquidity); -} - -function computeDeltaGivenDeltaLRoundUp( - uint256 reserve, - uint256 deltaLiquidity, - uint256 totalLiquidity -) pure returns (uint256) { - return reserve.mulWadUp(deltaLiquidity.divWadUp(totalLiquidity)); -} - -function computeDeltaGivenDeltaLRoundDown( - uint256 reserve, - uint256 deltaLiquidity, - uint256 totalLiquidity -) pure returns (uint256) { - return reserve.mulWadDown(deltaLiquidity.divWadDown(totalLiquidity)); -} From 5fcbba3008026ce926e448f98610ebac1c8b6787 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 20 Mar 2024 19:44:12 +0400 Subject: [PATCH 028/119] test: update test_ConstantSum_allocate_Works --- test/ConstantSum/unit/Allocate.t.sol | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/ConstantSum/unit/Allocate.t.sol b/test/ConstantSum/unit/Allocate.t.sol index 94c19cc6..3e21e32b 100644 --- a/test/ConstantSum/unit/Allocate.t.sol +++ b/test/ConstantSum/unit/Allocate.t.sol @@ -1,17 +1,21 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import { console } from "forge-std/console.sol"; import { ConstantSumSetUp } from "./SetUp.sol"; +import { + computeDeltaLiquidity, + ConstantSumParams +} from "src/ConstantSum/ConstantSumMath.sol"; contract ConstantSumAllocateTest is ConstantSumSetUp { -/* function test_ConstantSum_allocate_Works() public defaultPool { uint256 deltaX = 0.1 ether; uint256 deltaY = 0.1 ether; - (bool valid, bytes memory data) = - solver.simulateAllocate(POOL_ID, deltaX, deltaY); - console.log("valid", valid); + + ConstantSumParams memory params = + abi.decode(constantSum.getPoolParams(POOL_ID), (ConstantSumParams)); + + uint256 deltaL = computeDeltaLiquidity(deltaX, deltaY, params.price); + dfmm.allocate(POOL_ID, abi.encode(deltaX, deltaY, deltaL)); } - */ } From 94a304d353e67a8357702e6ee00f97f7d4dfe9a5 Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 21 Mar 2024 08:43:30 +0400 Subject: [PATCH 029/119] test: fix ConstantSum init test --- test/ConstantSum/ConstantSumTest.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ConstantSum/ConstantSumTest.t.sol b/test/ConstantSum/ConstantSumTest.t.sol index 3d7060c7..cb2bcc9d 100644 --- a/test/ConstantSum/ConstantSumTest.t.sol +++ b/test/ConstantSum/ConstantSumTest.t.sol @@ -111,7 +111,7 @@ contract ConstantSumTest is Test { assertEq(pool.reserves[0], 1 ether); assertEq(pool.reserves[1], 1 ether); - assertEq(pool.totalLiquidity, 1.5 ether); + assertEq(pool.totalLiquidity, 3 ether); } function test_constant_sum_swap_x_in_no_fee() public basic_feeless { From bd3c6807ad1ae681a8d91337435d70bb2e503500 Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 21 Mar 2024 08:46:01 +0400 Subject: [PATCH 030/119] test: add test_ConstantSum_init_TransfersTokens --- test/ConstantSum/unit/Init.t.sol | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/ConstantSum/unit/Init.t.sol b/test/ConstantSum/unit/Init.t.sol index f5792377..1d81a4dc 100644 --- a/test/ConstantSum/unit/Init.t.sol +++ b/test/ConstantSum/unit/Init.t.sol @@ -37,4 +37,54 @@ contract ConstantSumInitTest is ConstantSumSetUp { dfmm.init(initParams); } + + function test_ConstantSum_init_TransfersTokens() public { + uint256 price = 1 ether; + + ConstantSumParams memory params = ConstantSumParams({ + price: price, + swapFee: TEST_SWAP_FEE, + controller: address(this) + }); + + uint256 reserveX = 1 ether; + uint256 reserveY = 1 ether; + + bytes memory initData = + solver.getInitialPoolData(reserveX, reserveY, params); + + address[] memory tokens = new address[](2); + tokens[0] = address(tokenX); + tokens[1] = address(tokenY); + + InitParams memory initParams = InitParams({ + name: "", + symbol: "", + strategy: address(constantSum), + tokens: tokens, + data: initData, + feeCollector: address(0), + controllerFee: 0 + }); + + uint256 dfmmPreTokenXBalance = tokenX.balanceOf(address(dfmm)); + uint256 dfmmPreTokenYBalance = tokenY.balanceOf(address(dfmm)); + uint256 userPreTokenXBalance = tokenX.balanceOf(address(this)); + uint256 userPreTokenYBalance = tokenY.balanceOf(address(this)); + + dfmm.init(initParams); + + uint256 dfmmPostTokenXBalance = tokenX.balanceOf(address(dfmm)); + uint256 dfmmPostTokenYBalance = tokenY.balanceOf(address(dfmm)); + uint256 userPostTokenXBalance = tokenX.balanceOf(address(this)); + uint256 userPostTokenYBalance = tokenY.balanceOf(address(this)); + + assertEq(dfmmPreTokenXBalance + reserveX, dfmmPostTokenXBalance); + + assertEq(dfmmPreTokenYBalance + reserveY, dfmmPostTokenYBalance); + + assertEq(userPreTokenXBalance - reserveX, userPostTokenXBalance); + + assertEq(userPreTokenYBalance - reserveY, userPostTokenYBalance); + } } From b30f8b462a47348fbc3b8e69d4ab7c0135144dea Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 21 Mar 2024 08:48:37 +0400 Subject: [PATCH 031/119] test: update test_ConstantSum_init_InitializesPool --- test/ConstantSum/unit/Init.t.sol | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/ConstantSum/unit/Init.t.sol b/test/ConstantSum/unit/Init.t.sol index 1d81a4dc..1794f26b 100644 --- a/test/ConstantSum/unit/Init.t.sol +++ b/test/ConstantSum/unit/Init.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import { ConstantSumSetUp } from "./SetUp.sol"; import { ConstantSum, ConstantSumParams } from "src/ConstantSum/ConstantSum.sol"; -import { DFMM, IDFMM, InitParams } from "src/DFMM.sol"; +import { Pool, InitParams } from "src/interfaces/IDFMM.sol"; +import { ConstantSumSetUp } from "./SetUp.sol"; contract ConstantSumInitTest is ConstantSumSetUp { function test_ConstantSum_init_InitializesPool() public { @@ -35,7 +35,11 @@ contract ConstantSumInitTest is ConstantSumSetUp { controllerFee: 0 }); - dfmm.init(initParams); + (POOL_ID,,) = dfmm.init(initParams); + Pool memory pool = dfmm.pools(POOL_ID); + + assertEq(pool.reserves[0], reserveX); + assertEq(pool.reserves[1], reserveY); } function test_ConstantSum_init_TransfersTokens() public { @@ -80,11 +84,8 @@ contract ConstantSumInitTest is ConstantSumSetUp { uint256 userPostTokenYBalance = tokenY.balanceOf(address(this)); assertEq(dfmmPreTokenXBalance + reserveX, dfmmPostTokenXBalance); - assertEq(dfmmPreTokenYBalance + reserveY, dfmmPostTokenYBalance); - assertEq(userPreTokenXBalance - reserveX, userPostTokenXBalance); - assertEq(userPreTokenYBalance - reserveY, userPostTokenYBalance); } } From f82f84ce335b6ec2de1ec3ffb944c10e4eafc922 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 12:47:26 +0400 Subject: [PATCH 032/119] test: move ConstantSum init test to dedicated test file --- test/ConstantSum/ConstantSumTest.t.sol | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/ConstantSum/ConstantSumTest.t.sol b/test/ConstantSum/ConstantSumTest.t.sol index cb2bcc9d..cbe629be 100644 --- a/test/ConstantSum/ConstantSumTest.t.sol +++ b/test/ConstantSum/ConstantSumTest.t.sol @@ -100,20 +100,6 @@ contract ConstantSumTest is Test { _; } - function test_init() public basic { - (ConstantSumParams memory params) = - abi.decode(constantSum.getPoolParams(POOL_ID), (ConstantSumParams)); - assertEq(params.price, 2 ether); - assertEq(params.swapFee, 0.003 ether); - assertEq(params.controller, address(0)); - - Pool memory pool = dfmm.pools(POOL_ID); - - assertEq(pool.reserves[0], 1 ether); - assertEq(pool.reserves[1], 1 ether); - assertEq(pool.totalLiquidity, 3 ether); - } - function test_constant_sum_swap_x_in_no_fee() public basic_feeless { uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); From 4f29be90660aadf387745587489d3e39ba5251b9 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 12:47:35 +0400 Subject: [PATCH 033/119] test: add test_ConstantSum_init_StoresPoolParams --- test/ConstantSum/unit/Init.t.sol | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/ConstantSum/unit/Init.t.sol b/test/ConstantSum/unit/Init.t.sol index 1794f26b..ec4b9792 100644 --- a/test/ConstantSum/unit/Init.t.sol +++ b/test/ConstantSum/unit/Init.t.sol @@ -42,6 +42,47 @@ contract ConstantSumInitTest is ConstantSumSetUp { assertEq(pool.reserves[1], reserveY); } + // This test doesn't pass because the `controller` param is not stored + function test_ConstantSum_init_StoresPoolParams() public { + skip(); + + uint256 price = 1 ether; + + ConstantSumParams memory params = ConstantSumParams({ + price: price, + swapFee: TEST_SWAP_FEE, + controller: address(this) + }); + + uint256 reserveX = 1 ether; + uint256 reserveY = 1 ether; + + bytes memory initData = + solver.getInitialPoolData(reserveX, reserveY, params); + + address[] memory tokens = new address[](2); + tokens[0] = address(tokenX); + tokens[1] = address(tokenY); + + InitParams memory initParams = InitParams({ + name: "", + symbol: "", + strategy: address(constantSum), + tokens: tokens, + data: initData, + feeCollector: address(0), + controllerFee: 0 + }); + + (POOL_ID,,) = dfmm.init(initParams); + ConstantSumParams memory poolParams = + abi.decode(constantSum.getPoolParams(POOL_ID), (ConstantSumParams)); + + assertEq(poolParams.price, price); + assertEq(poolParams.swapFee, TEST_SWAP_FEE); + assertEq(poolParams.controller, address(this)); + } + function test_ConstantSum_init_TransfersTokens() public { uint256 price = 1 ether; From 9f6da08427761b854b392c802614466456604e87 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 13:41:12 +0400 Subject: [PATCH 034/119] test: move ConstantSum no swap fees tests --- test/ConstantSum/ConstantSumTest.t.sol | 47 ------------------------ test/ConstantSum/unit/SetUp.sol | 26 ++++++++++++++ test/ConstantSum/unit/Swap.t.sol | 49 +++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 48 deletions(-) diff --git a/test/ConstantSum/ConstantSumTest.t.sol b/test/ConstantSum/ConstantSumTest.t.sol index cbe629be..ef31ddbe 100644 --- a/test/ConstantSum/ConstantSumTest.t.sol +++ b/test/ConstantSum/ConstantSumTest.t.sol @@ -100,53 +100,6 @@ contract ConstantSumTest is Test { _; } - function test_constant_sum_swap_x_in_no_fee() public basic_feeless { - uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); - uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); - uint256 preUserBalanceX = tokenX.balanceOf(address(this)); - uint256 preUserBalanceY = tokenY.balanceOf(address(this)); - - bool isSwapXForY = true; - uint256 amountIn = 0.1 ether; - - (,, bytes memory swapData) = - solver.simulateSwap(POOL_ID, isSwapXForY, amountIn); - (,, uint256 inputAmount, uint256 outputAmount) = - dfmm.swap(POOL_ID, address(this), swapData); - - 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_constant_sum_swap_y_in_no_fee() public basic_feeless { - uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); - uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); - uint256 preUserBalanceX = tokenX.balanceOf(address(this)); - uint256 preUserBalanceY = tokenY.balanceOf(address(this)); - - bool isSwapXForY = false; - uint256 amountIn = 0.1 ether; - (,, bytes memory swapData) = - solver.simulateSwap(POOL_ID, isSwapXForY, amountIn); - (,, uint256 inputAmount, uint256 outputAmount) = - dfmm.swap(POOL_ID, address(this), swapData); - - assertEq( - tokenX.balanceOf(address(dfmm)), preDfmmBalanceX - outputAmount - ); - assertEq(tokenY.balanceOf(address(dfmm)), preDfmmBalanceY + inputAmount); - assertEq( - tokenX.balanceOf(address(this)), preUserBalanceX + outputAmount - ); - assertEq(tokenY.balanceOf(address(this)), preUserBalanceY - inputAmount); - } - function test_constant_sum_swap_x_in_invalid() public basic_feeless { bool xIn = true; uint256 amountIn = 1.1 ether; diff --git a/test/ConstantSum/unit/SetUp.sol b/test/ConstantSum/unit/SetUp.sol index 4a6acad2..0e7e3100 100644 --- a/test/ConstantSum/unit/SetUp.sol +++ b/test/ConstantSum/unit/SetUp.sol @@ -55,4 +55,30 @@ contract ConstantSumSetUp is SetUp { _; } + + modifier zeroFeePool() { + uint256 reserveX = 1 ether; + uint256 reserveY = 1 ether; + + bytes memory initData = + solver.getInitialPoolData(reserveX, reserveY, zeroFeeParams); + + address[] memory tokens = new address[](2); + tokens[0] = address(tokenX); + tokens[1] = address(tokenY); + + InitParams memory initParams = InitParams({ + name: "", + symbol: "", + strategy: address(constantSum), + tokens: tokens, + data: initData, + feeCollector: address(0), + controllerFee: 0 + }); + + (POOL_ID,,) = dfmm.init(initParams); + + _; + } } diff --git a/test/ConstantSum/unit/Swap.t.sol b/test/ConstantSum/unit/Swap.t.sol index 6c27f322..7f7ec1f3 100644 --- a/test/ConstantSum/unit/Swap.t.sol +++ b/test/ConstantSum/unit/Swap.t.sol @@ -3,4 +3,51 @@ pragma solidity ^0.8.13; import { ConstantSumSetUp } from "./SetUp.sol"; -contract ConstantSumSwapTest is ConstantSumSetUp { } +contract ConstantSumSwapTest is ConstantSumSetUp { + function test_ConstantSum_swap_SwapsXNoFee() public zeroFeePool { + uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); + uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); + uint256 preUserBalanceX = tokenX.balanceOf(address(this)); + uint256 preUserBalanceY = tokenY.balanceOf(address(this)); + + bool isSwapXForY = true; + uint256 amountIn = 0.1 ether; + + (,, bytes memory swapData) = + solver.simulateSwap(POOL_ID, isSwapXForY, amountIn); + (,, uint256 inputAmount, uint256 outputAmount) = + dfmm.swap(POOL_ID, address(this), swapData); + + 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_ConstantSum_swap_SwapsYNoFee() public zeroFeePool { + uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); + uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); + uint256 preUserBalanceX = tokenX.balanceOf(address(this)); + uint256 preUserBalanceY = tokenY.balanceOf(address(this)); + + bool isSwapXForY = false; + uint256 amountIn = 0.1 ether; + (,, bytes memory swapData) = + solver.simulateSwap(POOL_ID, isSwapXForY, amountIn); + (,, uint256 inputAmount, uint256 outputAmount) = + dfmm.swap(POOL_ID, address(this), swapData); + + assertEq( + tokenX.balanceOf(address(dfmm)), preDfmmBalanceX - outputAmount + ); + assertEq(tokenY.balanceOf(address(dfmm)), preDfmmBalanceY + inputAmount); + assertEq( + tokenX.balanceOf(address(this)), preUserBalanceX + outputAmount + ); + assertEq(tokenY.balanceOf(address(this)), preUserBalanceY - inputAmount); + } +} From c63ce0c809278d52326861e014a72774c2c4b256 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 14:47:01 +0400 Subject: [PATCH 035/119] test: move ConstantSum validateSwap tests to dedicated test file --- test/ConstantSum/ConstantSumTest.t.sol | 14 -------------- test/ConstantSum/unit/ValidateSwap.t.sol | 23 ++++++++++++++++++++++- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/test/ConstantSum/ConstantSumTest.t.sol b/test/ConstantSum/ConstantSumTest.t.sol index ef31ddbe..43a67025 100644 --- a/test/ConstantSum/ConstantSumTest.t.sol +++ b/test/ConstantSum/ConstantSumTest.t.sol @@ -100,20 +100,6 @@ contract ConstantSumTest is Test { _; } - function test_constant_sum_swap_x_in_invalid() public basic_feeless { - bool xIn = true; - uint256 amountIn = 1.1 ether; - vm.expectRevert(ConstantSumSolver.NotEnoughLiquidity.selector); - solver.simulateSwap(POOL_ID, xIn, amountIn); - } - - function test_constant_sum_swap_y_in_invalid() public basic_feeless { - bool xIn = false; - uint256 amountIn = 2.1 ether; - vm.expectRevert(ConstantSumSolver.NotEnoughLiquidity.selector); - solver.simulateSwap(POOL_ID, xIn, amountIn); - } - function test_constant_sum_swap_x_in_with_fee() public basic { uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); diff --git a/test/ConstantSum/unit/ValidateSwap.t.sol b/test/ConstantSum/unit/ValidateSwap.t.sol index 382faf8e..6327d061 100644 --- a/test/ConstantSum/unit/ValidateSwap.t.sol +++ b/test/ConstantSum/unit/ValidateSwap.t.sol @@ -1,6 +1,27 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; +import { ConstantSumSolver } from "src/ConstantSum/ConstantSumSolver.sol"; import { ConstantSumSetUp } from "./SetUp.sol"; -contract ConstantSumValidateSwapTest is ConstantSumSetUp { } +contract ConstantSumValidateSwapTest is ConstantSumSetUp { + function test_ConstantSum_simulateSwap_RevertsInvalidSwapX() + public + defaultPool + { + bool xIn = true; + uint256 amountIn = 1.1 ether; + vm.expectRevert(ConstantSumSolver.NotEnoughLiquidity.selector); + solver.simulateSwap(POOL_ID, xIn, amountIn); + } + + function test_ConstantSum_simulateSwap_RevertsInvalidSwapY() + public + defaultPool + { + bool xIn = false; + uint256 amountIn = 2.1 ether; + vm.expectRevert(ConstantSumSolver.NotEnoughLiquidity.selector); + solver.simulateSwap(POOL_ID, xIn, amountIn); + } +} From 8fbbc8c31c9553a20aea9555343f44fe1dccb0a9 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 14:48:34 +0400 Subject: [PATCH 036/119] test: move ConstantSum swap tests to dedicated test file --- test/ConstantSum/ConstantSumTest.t.sol | 45 ------------------------- test/ConstantSum/unit/Swap.t.sol | 46 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/test/ConstantSum/ConstantSumTest.t.sol b/test/ConstantSum/ConstantSumTest.t.sol index 43a67025..86466253 100644 --- a/test/ConstantSum/ConstantSumTest.t.sol +++ b/test/ConstantSum/ConstantSumTest.t.sol @@ -100,51 +100,6 @@ contract ConstantSumTest is Test { _; } - function test_constant_sum_swap_x_in_with_fee() public basic { - uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); - uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); - uint256 preUserBalanceX = tokenX.balanceOf(address(this)); - uint256 preUserBalanceY = tokenY.balanceOf(address(this)); - - bool isSwapXForY = true; - uint256 amountIn = 0.1 ether; - (,, bytes memory swapData) = - solver.simulateSwap(POOL_ID, isSwapXForY, amountIn); - (,, uint256 inputAmount, uint256 outputAmount) = - dfmm.swap(POOL_ID, address(this), swapData); - - 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_constant_sum_swap_y_in_with_fee() public basic { - uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); - uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); - uint256 preUserBalanceX = tokenX.balanceOf(address(this)); - uint256 preUserBalanceY = tokenY.balanceOf(address(this)); - - bool isSwapXForY = false; - uint256 amountIn = 0.1 ether; - (,, bytes memory swapData) = - solver.simulateSwap(POOL_ID, isSwapXForY, amountIn); - (,, uint256 inputAmount, uint256 outputAmount) = - dfmm.swap(POOL_ID, address(this), swapData); - - assertEq( - tokenX.balanceOf(address(dfmm)), preDfmmBalanceX - outputAmount - ); - assertEq(tokenY.balanceOf(address(dfmm)), preDfmmBalanceY + inputAmount); - assertEq( - tokenX.balanceOf(address(this)), preUserBalanceX + outputAmount - ); - assertEq(tokenY.balanceOf(address(this)), preUserBalanceY - inputAmount); - } /* function test_constant_sum_allocate() public basic { uint256 poolId = dfmm.nonce() - 1; diff --git a/test/ConstantSum/unit/Swap.t.sol b/test/ConstantSum/unit/Swap.t.sol index 7f7ec1f3..17384f7c 100644 --- a/test/ConstantSum/unit/Swap.t.sol +++ b/test/ConstantSum/unit/Swap.t.sol @@ -28,6 +28,52 @@ contract ConstantSumSwapTest is ConstantSumSetUp { ); } + function test_ConstantSum_swap_SwapsX() public defaultPool { + uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); + uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); + uint256 preUserBalanceX = tokenX.balanceOf(address(this)); + uint256 preUserBalanceY = tokenY.balanceOf(address(this)); + + bool isSwapXForY = true; + uint256 amountIn = 0.1 ether; + (,, bytes memory swapData) = + solver.simulateSwap(POOL_ID, isSwapXForY, amountIn); + (,, uint256 inputAmount, uint256 outputAmount) = + dfmm.swap(POOL_ID, address(this), swapData); + + 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_ConstantSum_swap_SwapsY() public defaultPool { + uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); + uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); + uint256 preUserBalanceX = tokenX.balanceOf(address(this)); + uint256 preUserBalanceY = tokenY.balanceOf(address(this)); + + bool isSwapXForY = false; + uint256 amountIn = 0.1 ether; + (,, bytes memory swapData) = + solver.simulateSwap(POOL_ID, isSwapXForY, amountIn); + (,, uint256 inputAmount, uint256 outputAmount) = + dfmm.swap(POOL_ID, address(this), swapData); + + assertEq( + tokenX.balanceOf(address(dfmm)), preDfmmBalanceX - outputAmount + ); + assertEq(tokenY.balanceOf(address(dfmm)), preDfmmBalanceY + inputAmount); + assertEq( + tokenX.balanceOf(address(this)), preUserBalanceX + outputAmount + ); + assertEq(tokenY.balanceOf(address(this)), preUserBalanceY - inputAmount); + } + function test_ConstantSum_swap_SwapsYNoFee() public zeroFeePool { uint256 preDfmmBalanceX = tokenX.balanceOf(address(dfmm)); uint256 preDfmmBalanceY = tokenY.balanceOf(address(dfmm)); From a946a7cd442114971bc14856a8911fb02fabebb0 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 14:49:00 +0400 Subject: [PATCH 037/119] test: remove old ConstantSumTest --- test/ConstantSum/ConstantSumTest.t.sol | 159 ------------------------- 1 file changed, 159 deletions(-) delete mode 100644 test/ConstantSum/ConstantSumTest.t.sol diff --git a/test/ConstantSum/ConstantSumTest.t.sol b/test/ConstantSum/ConstantSumTest.t.sol deleted file mode 100644 index 86466253..00000000 --- a/test/ConstantSum/ConstantSumTest.t.sol +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "solmate/test/utils/mocks/MockERC20.sol"; -import "src/DFMM.sol"; -import "src/ConstantSum/ConstantSum.sol"; -import "src/ConstantSum/ConstantSumSolver.sol"; - -contract ConstantSumTest is Test { - using stdStorage for StdStorage; - - DFMM dfmm; - ConstantSum constantSum; - ConstantSumSolver solver; - MockERC20 tokenX; - MockERC20 tokenY; - - uint256 POOL_ID; - - uint256 public constant TEST_ZERO_FEE = 0; - uint256 public constant TEST_SWAP_FEE = 0.003 ether; - - function setUp() public { - tokenX = new MockERC20("tokenX", "X", 18); - tokenY = new MockERC20("tokenY", "Y", 18); - tokenX.mint(address(this), 100_000_000 ether); - tokenY.mint(address(this), 100_000_000 ether); - - dfmm = new DFMM(address(0)); - constantSum = new ConstantSum(address(dfmm)); - solver = new ConstantSumSolver(address(constantSum)); - tokenX.approve(address(dfmm), type(uint256).max); - tokenY.approve(address(dfmm), type(uint256).max); - } - - modifier basic_feeless() { - vm.warp(0); - - ConstantSumParams memory params = ConstantSumParams({ - price: ONE * 2, - swapFee: 0, - controller: address(0) - }); - - uint256 init_x = ONE * 1; - uint256 init_y = ONE * 1; - - bytes memory initData = - solver.getInitialPoolData(init_x, init_y, params); - - address[] memory tokens = new address[](2); - tokens[0] = address(tokenX); - tokens[1] = address(tokenY); - - InitParams memory initParams = InitParams({ - name: "", - symbol: "", - strategy: address(constantSum), - tokens: tokens, - data: initData, - feeCollector: address(0), - controllerFee: 0 - }); - - (POOL_ID,,) = dfmm.init(initParams); - _; - } - - modifier basic() { - vm.warp(0); - - ConstantSumParams memory params = ConstantSumParams({ - price: ONE * 2, - swapFee: TEST_SWAP_FEE, - controller: address(0) - }); - - uint256 init_x = ONE * 1; - uint256 init_y = ONE * 1; - - bytes memory initData = - solver.getInitialPoolData(init_x, init_y, params); - - address[] memory tokens = new address[](2); - tokens[0] = address(tokenX); - tokens[1] = address(tokenY); - - InitParams memory initParams = InitParams({ - name: "", - symbol: "", - strategy: address(constantSum), - tokens: tokens, - data: initData, - feeCollector: address(0), - controllerFee: 0 - }); - - (POOL_ID,,) = dfmm.init(initParams); - _; - } - - /* - function test_constant_sum_allocate() public basic { - uint256 poolId = dfmm.nonce() - 1; - uint256 amountX = 0.1 ether; - uint256 amountY = 0.1 ether; - (bool valid, bytes memory swapData) = - solver.simulateAllocate(poolId, amountX, amountY); - console2.log("Valid: ", valid); - assert(valid); - - (uint256 endReservesRx, uint256 endReservesRy, uint256 endReservesL) = - abi.decode(swapData, (uint256, uint256, uint256)); - - console2.log("endReservesRx: ", endReservesRx); - assertEq(endReservesRx, 1.1 ether); - - console2.log("endReservesRy: ", endReservesRy); - assertEq(endReservesRy, 1.1 ether); - - console2.log("endReservesL: ", endReservesL); - assertEq(endReservesL, 1.65 ether); - - dfmm.allocate(poolId, swapData); - } - - function test_constant_sum_deallocate() public basic { - uint256 poolId = dfmm.nonce() - 1; - uint256 amountX = 0.1 ether; - uint256 amountY = 0.1 ether; - (, bytes memory swapData) = - solver.simulateDeallocate(poolId, amountX, amountY); - dfmm.deallocate(poolId, swapData); - } - - function test_constant_sum_fail_deallocate() public basic { - uint256 poolId = dfmm.nonce() - 1; - uint256 amountX = 1.2 ether; - uint256 amountY = 1.2 ether; - vm.expectRevert(ConstantSumSolver.NotEnoughLiquidity.selector); - solver.simulateDeallocate(poolId, amountX, amountY); - } - - function test_constant_sum_price_update() public basic { - uint256 poolId = dfmm.nonce() - 1; - uint256 newPrice = 3 ether; - bytes memory data = encodePriceUpdate(newPrice); - - vm.prank(address(0)); - DFMM(dfmm).update(poolId, data); - - (ConstantSumParams memory newParams) = abi.decode( - constantSum.getPoolParams(poolId), (ConstantSumParams) - ); - assertEq(newParams.price, 3 ether); - } - */ -} From 1fe5af1bf3f8fc6ac70f393ee78053cd4053d9f4 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 14:53:14 +0400 Subject: [PATCH 038/119] test: add ConstantSum update tests --- test/ConstantSum/unit/Update.t.sol | 36 ++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/test/ConstantSum/unit/Update.t.sol b/test/ConstantSum/unit/Update.t.sol index 8ed75a73..26312886 100644 --- a/test/ConstantSum/unit/Update.t.sol +++ b/test/ConstantSum/unit/Update.t.sol @@ -1,6 +1,38 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import { ConstantSumSetUp } from "./SetUp.sol"; +import { ConstantSumSetUp, ConstantSumParams } from "./SetUp.sol"; +import { + encodeFeeUpdate, + encodePriceUpdate, + encodeControllerUpdate +} from "src/ConstantSum/ConstantSumUtils.sol"; -contract ConstantSumUpdateTest is ConstantSumSetUp { } +contract ConstantSumUpdateTest is ConstantSumSetUp { + function test_ConstantSum_update_SetsSwapFee() public defaultPool { + skip(); + uint256 newSwapFee = 0.004 ether; + dfmm.update(POOL_ID, encodeFeeUpdate(newSwapFee)); + ConstantSumParams memory poolParams = + abi.decode(constantSum.getPoolParams(POOL_ID), (ConstantSumParams)); + assertEq(poolParams.swapFee, newSwapFee); + } + + function test_ConstantSum_update_SetsPrice() public defaultPool { + skip(); + uint256 newPrice = 3 ether; + dfmm.update(POOL_ID, encodePriceUpdate(newPrice)); + ConstantSumParams memory poolParams = + abi.decode(constantSum.getPoolParams(POOL_ID), (ConstantSumParams)); + assertEq(poolParams.price, newPrice); + } + + function test_ConstantSum_update_SetsController() public defaultPool { + skip(); + address newController = address(this); + dfmm.update(POOL_ID, encodeControllerUpdate(newController)); + ConstantSumParams memory poolParams = + abi.decode(constantSum.getPoolParams(POOL_ID), (ConstantSumParams)); + assertEq(poolParams.controller, newController); + } +} From fb905596bef562e19a3f60c3cd96fd6d24d87f0f Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 15:00:34 +0400 Subject: [PATCH 039/119] test: add test_ConstantSum_getPoolParams_ReturnsPoolParams --- test/ConstantSum/unit/GetPoolParams.t.sol | 42 +++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/test/ConstantSum/unit/GetPoolParams.t.sol b/test/ConstantSum/unit/GetPoolParams.t.sol index 8381be67..ced8ae4c 100644 --- a/test/ConstantSum/unit/GetPoolParams.t.sol +++ b/test/ConstantSum/unit/GetPoolParams.t.sol @@ -1,6 +1,44 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import { ConstantSumSetUp } from "./SetUp.sol"; +import { ConstantSumParams } from "src/ConstantSum/ConstantSum.sol"; +import { ConstantSumSetUp, InitParams } from "./SetUp.sol"; -contract ConstantSumGetPoolParamsTest is ConstantSumSetUp { } +contract ConstantSumGetPoolParamsTest is ConstantSumSetUp { + function test_ConstantSum_getPoolParams_ReturnsPoolParams() public { + skip(); + ConstantSumParams memory initPoolParams = ConstantSumParams({ + price: 2 ether, + swapFee: TEST_SWAP_FEE, + controller: address(this) + }); + + uint256 reserveX = 1 ether; + uint256 reserveY = 1 ether; + + bytes memory initData = + solver.getInitialPoolData(reserveX, reserveY, defaultParams); + + address[] memory tokens = new address[](2); + tokens[0] = address(tokenX); + tokens[1] = address(tokenY); + + InitParams memory initParams = InitParams({ + name: "", + symbol: "", + strategy: address(constantSum), + tokens: tokens, + data: initData, + feeCollector: address(0), + controllerFee: 0 + }); + + (POOL_ID,,) = dfmm.init(initParams); + + ConstantSumParams memory poolParams = + abi.decode(constantSum.getPoolParams(POOL_ID), (ConstantSumParams)); + assertEq(poolParams.swapFee, initPoolParams.swapFee); + assertEq(poolParams.price, initPoolParams.price); + assertEq(poolParams.controller, initPoolParams.controller); + } +} From 8d8f8cb3e47e8c2f13022c3d160ef823fa3c2e1e Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 15:13:22 +0400 Subject: [PATCH 040/119] test: add test_ConstantSum_constructor --- test/ConstantSum/unit/Constructor.t.sol | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/ConstantSum/unit/Constructor.t.sol b/test/ConstantSum/unit/Constructor.t.sol index 136878bb..026e56d3 100644 --- a/test/ConstantSum/unit/Constructor.t.sol +++ b/test/ConstantSum/unit/Constructor.t.sol @@ -1,6 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import { ConstantSumSetUp } from "./SetUp.sol"; +import { ConstantSumSetUp, ConstantSum } from "./SetUp.sol"; -contract ConstantSumConstructorTest is ConstantSumSetUp { } +contract ConstantSumConstructorTest is ConstantSumSetUp { + function test_ConstantSum_constructor() public { + ConstantSum constantSum = new ConstantSum(address(dfmm)); + assertEq(constantSum.dfmm(), address(dfmm)); + } +} From 0903141b64091fcecfaab14f8504bb2eaee96e62 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 16:39:13 +0400 Subject: [PATCH 041/119] fix: add InvalidReservesLength error to PairStrategy --- src/interfaces/IStrategy.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/interfaces/IStrategy.sol b/src/interfaces/IStrategy.sol index 40027f55..0b10cd9a 100644 --- a/src/interfaces/IStrategy.sol +++ b/src/interfaces/IStrategy.sol @@ -23,6 +23,9 @@ interface IStrategy { /// @dev Thrown when an expected delta does not match the actual delta. error DeltaError(uint256 expected, uint256 actual); + /// @dev Thrown when the reserves length is not 2. + error InvalidReservesLength(); + // Setters /** From af737451449b4d302fe19393caa0cd9449095cbd Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 16:39:32 +0400 Subject: [PATCH 042/119] fix: check reserves and tokens array length in ConstantSum --- src/ConstantSum/ConstantSum.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index 08a1c27d..37a5f358 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -45,7 +45,7 @@ contract ConstantSum is PairStrategy { function init( address, uint256 poolId, - Pool calldata, + Pool calldata pool, bytes calldata data ) public @@ -61,6 +61,10 @@ contract ConstantSum is PairStrategy { (reserves, totalLiquidity, params) = abi.decode(data, (uint256[], uint256, ConstantSumParams)); + if (pool.reserves.length != 2 || reserves.length != 2) { + revert InvalidReservesLength(); + } + internalParams[poolId].price = params.price; internalParams[poolId].swapFee = params.swapFee; From ca780dcb9498536cd533dd7800363b43d1deb367 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 18:35:30 +0400 Subject: [PATCH 043/119] fix: add pool reserves length check in G3M --- src/GeometricMean/GeometricMean.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/GeometricMean/GeometricMean.sol b/src/GeometricMean/GeometricMean.sol index 1dcf1942..44e372bc 100644 --- a/src/GeometricMean/GeometricMean.sol +++ b/src/GeometricMean/GeometricMean.sol @@ -74,7 +74,7 @@ contract GeometricMean is PairStrategy { function init( address, uint256 poolId, - Pool calldata, + Pool calldata pool, bytes calldata data ) external onlyDFMM returns (bool, int256, uint256[] memory, uint256) { InitState memory state; @@ -92,6 +92,10 @@ contract GeometricMean is PairStrategy { data, (uint256, uint256, uint256, uint256, uint256, address) ); + if (pool.reserves.length != 2) { + revert InvalidReservesLength(); + } + if (state.wX >= ONE) { revert InvalidWeightX(); } From 3bd1bc3d94dd1f2447c01b88b89e15c79bb5187a Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 18:37:16 +0400 Subject: [PATCH 044/119] fix: add reserves and tokens array length check in LogNormal --- src/LogNormal/LogNormal.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/LogNormal/LogNormal.sol b/src/LogNormal/LogNormal.sol index 1bcd5513..ccdb3e1a 100644 --- a/src/LogNormal/LogNormal.sol +++ b/src/LogNormal/LogNormal.sol @@ -58,7 +58,7 @@ contract LogNormal is PairStrategy { function init( address, uint256 poolId, - Pool calldata, + Pool calldata pool, bytes calldata data ) public @@ -74,6 +74,10 @@ contract LogNormal is PairStrategy { (reserves, totalLiquidity, params) = abi.decode(data, (uint256[], uint256, LogNormalParams)); + if (pool.reserves.length != 2 || reserves.length != 2) { + revert InvalidReservesLength(); + } + internalParams[poolId].mean.lastComputedValue = params.mean; internalParams[poolId].width.lastComputedValue = params.width; internalParams[poolId].swapFee = params.swapFee; From 6154ecd000914d061514402b3a24397d25c04d01 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 18:42:57 +0400 Subject: [PATCH 045/119] fix: remove reserveX and reserveY in G3M init struct --- src/GeometricMean/GeometricMean.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/GeometricMean/GeometricMean.sol b/src/GeometricMean/GeometricMean.sol index 1dcf1942..7c7e87fb 100644 --- a/src/GeometricMean/GeometricMean.sol +++ b/src/GeometricMean/GeometricMean.sol @@ -61,8 +61,6 @@ contract GeometricMean is PairStrategy { struct InitState { bool valid; int256 invariant; - uint256 reserveX; - uint256 reserveY; address controller; uint256 swapFee; uint256 wX; From 63ed52118d0615564cf888163b1f5f23cf602467 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 22 Mar 2024 18:52:20 +0400 Subject: [PATCH 046/119] fix: use mulDiv to compute G3M deltaLiquidity --- src/GeometricMean/G3MMath.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GeometricMean/G3MMath.sol b/src/GeometricMean/G3MMath.sol index 0e409569..f0d0d251 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -26,7 +26,7 @@ function computeDeltaGivenDeltaLRoundUp( uint256 deltaLiquidity, uint256 totalLiquidity ) pure returns (uint256) { - return reserve.mulWadUp(deltaLiquidity.divWadUp(totalLiquidity)); + return reserve.mulDivUp(deltaLiquidity, totalLiquidity); } function computeDeltaGivenDeltaLRoundDown( @@ -34,7 +34,7 @@ function computeDeltaGivenDeltaLRoundDown( uint256 deltaLiquidity, uint256 totalLiquidity ) pure returns (uint256) { - return reserve.mulWadDown(deltaLiquidity.divWadDown(totalLiquidity)); + return reserve.mulDivDown(deltaLiquidity, totalLiquidity); } function computeLGivenX( From 22967e49380d76a135c5f92c2565fa90fa3cd6eb Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 25 Mar 2024 15:26:30 +0400 Subject: [PATCH 047/119] test: fix test_ConstantSum_getPoolParams_ReturnsPoolParams --- test/ConstantSum/unit/GetPoolParams.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ConstantSum/unit/GetPoolParams.t.sol b/test/ConstantSum/unit/GetPoolParams.t.sol index 47d61f5a..7213df12 100644 --- a/test/ConstantSum/unit/GetPoolParams.t.sol +++ b/test/ConstantSum/unit/GetPoolParams.t.sol @@ -16,7 +16,7 @@ contract ConstantSumGetPoolParamsTest is ConstantSumSetUp { uint256 reserveY = 1 ether; bytes memory initData = - solver.getInitialPoolData(reserveX, reserveY, defaultParams); + solver.getInitialPoolData(reserveX, reserveY, initPoolParams); address[] memory tokens = new address[](2); tokens[0] = address(tokenX); From e3f07d2b3d5a313164f2e0093fab166a8557f3c4 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 25 Mar 2024 17:17:43 +0400 Subject: [PATCH 048/119] feat: G3M init pool data now expects array of reserves --- src/GeometricMean/G3MMath.sol | 7 +++++-- src/GeometricMean/GeometricMean.sol | 11 +++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/GeometricMean/G3MMath.sol b/src/GeometricMean/G3MMath.sol index 0e409569..d5aff9e3 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -251,6 +251,9 @@ function computeInitialPoolData( L = computeNextLiquidity(amountX, rY, invariant, L, params); - return - abi.encode(amountX, rY, L, params.wX, params.swapFee, params.controller); + uint256[] memory reserves = new uint256[](2); + reserves[0] = amountX; + reserves[1] = rY; + + return abi.encode(reserves, L, params.wX, params.swapFee, params.controller); } diff --git a/src/GeometricMean/GeometricMean.sol b/src/GeometricMean/GeometricMean.sol index 44e372bc..3d20d9b2 100644 --- a/src/GeometricMean/GeometricMean.sol +++ b/src/GeometricMean/GeometricMean.sol @@ -79,20 +79,15 @@ contract GeometricMean is PairStrategy { ) external onlyDFMM returns (bool, int256, uint256[] memory, uint256) { InitState memory state; - state.reserves = new uint256[](2); - ( - state.reserves[0], - state.reserves[1], + state.reserves, state.totalLiquidity, state.wX, state.swapFee, state.controller - ) = abi.decode( - data, (uint256, uint256, uint256, uint256, uint256, address) - ); + ) = abi.decode(data, (uint256[], uint256, uint256, uint256, address)); - if (pool.reserves.length != 2) { + if (pool.reserves.length != 2 || state.reserves.length != 2) { revert InvalidReservesLength(); } From 6e84fcf89b190189281f4613e37460419e927ee9 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 26 Mar 2024 11:55:15 +0400 Subject: [PATCH 049/119] feat: update _transferFrom to use arrays of token and address --- src/DFMM.sol | 61 +++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index fb07e6a4..a47c313d 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -124,10 +124,10 @@ contract DFMM is IDFMM { if (decimals > 18 || decimals < 6) { revert InvalidTokenDecimals(); } - - _transferFrom(params.tokens[i], reserves[i]); } + _transferFrom(params.tokens, reserves); + emit Init( msg.sender, pool.strategy, @@ -166,9 +166,7 @@ contract DFMM is IDFMM { _manageTokens(msg.sender, poolId, true, deltaLiquidity); _pools[poolId].totalLiquidity += deltaLiquidity; - for (uint256 i = 0; i < length; i++) { - _transferFrom(_pools[poolId].tokens[i], deltas[i]); - } + _transferFrom(_pools[poolId].tokens, deltas); emit Allocate(msg.sender, poolId, deltas, deltaLiquidity); return deltas; @@ -254,7 +252,12 @@ contract DFMM is IDFMM { address tokenIn = _pools[poolId].tokens[state.tokenInIndex]; address tokenOut = _pools[poolId].tokens[state.tokenOutIndex]; - _transferFrom(tokenIn, state.amountIn); + address[] memory tokens = new address[](1); + tokens[0] = tokenIn; + uint256[] memory amounts = new uint256[](1); + amounts[0] = state.amountIn; + _transferFrom(tokens, amounts); + _transfer(tokenOut, recipient, state.amountOut); emit Swap( @@ -280,39 +283,43 @@ contract DFMM is IDFMM { // Internals /** - * @dev Transfers `amount` of `token` from the sender to the contract. Note - * that if ETH is present in the contract, it will be wrapped to WETH. Any - * excess of ETH will be sent back to the sender. - * @param token Address of the token to transfer. - * @param amount Amount to transfer expressed in WAD. + * @dev Transfers `amounts` of `tokens` from the sender to the contract. Note + * that if any ETH is present in the contract, it will be wrapped to WETH and + * used if sufficient. Any excess of ETH will be sent back to the sender. + * @param tokens An array of token addresses to transfer. + * @param amounts An array of amounts to transfer expressed in WAD. */ - function _transferFrom(address token, uint256 amount) internal { - if (token == weth) { - if (address(this).balance >= amount) { + function _transferFrom( + address[] memory tokens, + uint256[] memory amounts + ) internal { + uint256 length = tokens.length; + + for (uint256 i = 0; i < length; i++) { + address token = tokens[i]; + uint256 amount = amounts[i]; + + uint256 downscaledAmount = + downscaleUp(amount, computeScalingFactor(token)); + uint256 preBalance = ERC20(token).balanceOf(address(this)); + + if (token == weth && address(this).balance >= amount) { WETH(payable(weth)).deposit{ value: amount }(); } else { SafeTransferLib.safeTransferFrom( - ERC20(token), msg.sender, address(this), amount + ERC20(token), msg.sender, address(this), downscaledAmount ); } - if (address(this).balance > 0) { - SafeTransferLib.safeTransferETH( - msg.sender, address(this).balance - ); - } - } else { - uint256 downscaledAmount = - downscaleUp(amount, computeScalingFactor(token)); - uint256 preBalance = ERC20(token).balanceOf(address(this)); - SafeTransferLib.safeTransferFrom( - ERC20(token), msg.sender, address(this), downscaledAmount - ); uint256 postBalance = ERC20(token).balanceOf(address(this)); if (postBalance < preBalance + downscaledAmount) { revert InvalidTransfer(); } } + + if (address(this).balance > 0) { + SafeTransferLib.safeTransferETH(msg.sender, address(this).balance); + } } /** From 3e9497cb4bc93bb78b286f310c6c8497217dbad4 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 26 Mar 2024 11:55:28 +0400 Subject: [PATCH 050/119] test: update _transferFrom tests --- test/DFMM/unit/Internal.t.sol | 83 +++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/test/DFMM/unit/Internal.t.sol b/test/DFMM/unit/Internal.t.sol index db77e321..7d0226f4 100644 --- a/test/DFMM/unit/Internal.t.sol +++ b/test/DFMM/unit/Internal.t.sol @@ -8,8 +8,11 @@ import { ERC20WithFees } from "test/utils/ERC20WithFees.sol"; contract DFMMInternal is DFMM { constructor(address weth_) DFMM(weth_) { } - function transferFrom(address token, uint256 amount) external payable { - _transferFrom(token, amount); + function transferFrom( + address[] memory tokens, + uint256[] memory amounts + ) external payable { + _transferFrom(tokens, amounts); } function transfer(address token, address to, uint256 amount) external { @@ -28,38 +31,50 @@ contract DFMMInternalTest is DFMMSetUp { } function test_DFMM_transferFrom_WrapsETH() public { - uint256 amount = 1 ether; - dfmmInternal.transferFrom{ value: amount }(address(weth), amount); - assertEq(weth.balanceOf(address(dfmmInternal)), amount); + address[] memory tokens = new address[](1); + tokens[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1 ether; + dfmmInternal.transferFrom{ value: amounts[0] }(tokens, amounts); + assertEq(weth.balanceOf(address(dfmmInternal)), amounts[0]); assertEq(address(weth).balance, 1 ether); assertEq(address(dfmmInternal).balance, 0); } function test_DFMM_transferFrom_RefundsExtraETH() public { - uint256 amount = 1 ether; - dfmmInternal.transferFrom{ value: amount * 2 }(address(weth), amount); - assertEq(weth.balanceOf(address(dfmmInternal)), amount); + address[] memory tokens = new address[](1); + tokens[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1 ether; + dfmmInternal.transferFrom{ value: amounts[0] * 2 }(tokens, amounts); + assertEq(weth.balanceOf(address(dfmmInternal)), amounts[0]); assertEq(address(weth).balance, 1 ether); assertEq(address(dfmmInternal).balance, 0); } function test_DFMM_transferFrom_UsesWETH() public { - uint256 amount = 1 ether; - weth.deposit{ value: amount }(); - weth.approve(address(dfmmInternal), amount); - dfmmInternal.transferFrom(address(weth), amount); - assertEq(weth.balanceOf(address(dfmmInternal)), amount); - assertEq(address(weth).balance, amount); + address[] memory tokens = new address[](1); + tokens[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1 ether; + weth.deposit{ value: amounts[0] }(); + weth.approve(address(dfmmInternal), amounts[0]); + dfmmInternal.transferFrom(tokens, amounts); + assertEq(weth.balanceOf(address(dfmmInternal)), amounts[0]); + assertEq(address(weth).balance, amounts[0]); assertEq(address(dfmmInternal).balance, 0); } function test_DFMM_transferFrom_UsesWETHAndRefunds() public { - uint256 amount = 1 ether; - weth.deposit{ value: amount }(); - weth.approve(address(dfmmInternal), amount); - dfmmInternal.transferFrom{ value: amount - 1 }(address(weth), amount); - assertEq(weth.balanceOf(address(dfmmInternal)), amount); - assertEq(address(weth).balance, amount); + address[] memory tokens = new address[](1); + tokens[0] = address(weth); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1 ether; + weth.deposit{ value: amounts[0] }(); + weth.approve(address(dfmmInternal), amounts[0]); + dfmmInternal.transferFrom{ value: amounts[0] - 1 }(tokens, amounts); + assertEq(weth.balanceOf(address(dfmmInternal)), amounts[0]); + assertEq(address(weth).balance, amounts[0]); assertEq(address(dfmmInternal).balance, 0); } @@ -70,11 +85,15 @@ contract DFMMInternalTest is DFMMSetUp { 115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_458 ); MockERC20 token = new MockERC20("", "", 18); + address[] memory tokens = new address[](1); + tokens[0] = address(token); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amount; token.mint(address(this), amount); token.approve(address(dfmmInternal), amount); uint256 preDFMMBalance = token.balanceOf(address(dfmmInternal)); uint256 preThisBalance = token.balanceOf(address(this)); - dfmmInternal.transferFrom(address(token), amount); + dfmmInternal.transferFrom(tokens, amounts); assertEq( token.balanceOf(address(dfmmInternal)), preDFMMBalance + amount ); @@ -82,14 +101,18 @@ contract DFMMInternalTest is DFMMSetUp { } function test_DFMM_transferFrom_ScalesAmount() public { - uint256 amount = 1_000_000 * 10 ** 18; - uint256 scaledDownAmount = 1_000_000 * 10 ** 6; + address[] memory tokens = new address[](1); MockERC20 token = new MockERC20("", "", 6); + tokens[0] = address(token); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1_000_000 * 10 ** 18; + uint256 scaledDownAmount = 1_000_000 * 10 ** 6; + token.mint(address(this), scaledDownAmount); token.approve(address(dfmmInternal), scaledDownAmount); uint256 preDFMMBalance = token.balanceOf(address(dfmmInternal)); uint256 preThisBalance = token.balanceOf(address(this)); - dfmmInternal.transferFrom(address(token), amount); + dfmmInternal.transferFrom(tokens, amounts); assertEq( token.balanceOf(address(dfmmInternal)), preDFMMBalance + scaledDownAmount @@ -100,12 +123,16 @@ contract DFMMInternalTest is DFMMSetUp { } function test_DFMM_transferFrom_RevertsIfBalanceIsInsufficient() public { + address[] memory tokens = new address[](1); ERC20WithFees token = new ERC20WithFees("", "", 18, 500); - uint256 amount = 1 ether; - token.mint(address(this), amount); - token.approve(address(dfmmInternal), amount); + tokens[0] = address(token); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1 ether; + + token.mint(address(this), amounts[0]); + token.approve(address(dfmmInternal), amounts[0]); vm.expectRevert(IDFMM.InvalidTransfer.selector); - dfmmInternal.transferFrom(address(token), amount); + dfmmInternal.transferFrom(tokens, amounts); } function test_DFMM_transfer_UnwrapsETH() public { From de6d27bcf4c5726e3ba38a1729df2930d065f14e Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 26 Mar 2024 17:50:48 +0400 Subject: [PATCH 051/119] fix: remove unchecked scope for toUint --- src/LogNormal/LogNormalUtils.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/LogNormal/LogNormalUtils.sol b/src/LogNormal/LogNormalUtils.sol index 55e39843..e7a59a8f 100644 --- a/src/LogNormal/LogNormalUtils.sol +++ b/src/LogNormal/LogNormalUtils.sol @@ -79,8 +79,6 @@ function computeInitialPoolData( /// @dev Casts a positived signed integer to an unsigned integer, reverting if `x` is negative. function toUint(int256 x) pure returns (uint256) { - unchecked { - require(x >= 0, "toUint: negative"); - return uint256(x); - } + require(x >= 0, "toUint: negative"); + return uint256(x); } From 8aebaaec85343edd69bb47a8267fa130160ebe06 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 26 Mar 2024 17:54:20 +0400 Subject: [PATCH 052/119] fix: use mulDiv to compute deltas in LogNormalMath --- src/LogNormal/LogNormalMath.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LogNormal/LogNormalMath.sol b/src/LogNormal/LogNormalMath.sol index 5a5c9a08..308945dd 100644 --- a/src/LogNormal/LogNormalMath.sol +++ b/src/LogNormal/LogNormalMath.sol @@ -31,7 +31,7 @@ function computeDeltaGivenDeltaLRoundUp( uint256 deltaLiquidity, uint256 totalLiquidity ) pure returns (uint256) { - return reserve.mulWadUp(deltaLiquidity.divWadUp(totalLiquidity)); + return reserve.mulDivUp(deltaLiquidity, totalLiquidity); } function computeDeltaGivenDeltaLRoundDown( @@ -39,7 +39,7 @@ function computeDeltaGivenDeltaLRoundDown( uint256 deltaLiquidity, uint256 totalLiquidity ) pure returns (uint256) { - return reserve.mulWadDown(deltaLiquidity.divWadDown(totalLiquidity)); + return reserve.mulDivDown(deltaLiquidity, totalLiquidity); } function computeLnSDivK(uint256 S, uint256 K) pure returns (int256 lnSDivK) { From d18948cb3f600b2aa57fc921be240a7c030e16b3 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 26 Mar 2024 18:15:40 +0400 Subject: [PATCH 053/119] fix: max values for width and mean in LogNormal --- src/LogNormal/LogNormal.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/LogNormal/LogNormal.sol b/src/LogNormal/LogNormal.sol index 1bcd5513..db032b4a 100644 --- a/src/LogNormal/LogNormal.sol +++ b/src/LogNormal/LogNormal.sol @@ -39,6 +39,15 @@ struct LogNormalParams { address controller; } +/// @dev Thrown when the mean paramter is larger than the maximum. +error MeanTooLarge(); + +/// @dev Thrown when the width paramter is larger than the maximum. +error WidthTooLarge(); + +uint256 constant MAX_WIDTH = uint256(type(int256).max); +uint256 constant MAX_MEAN = uint256(type(int256).max); + /** * @title LogNormal Strategy for DFMM. * @author Primitive From 5a373802835e3e4fddcdda62e1aa931f2a00117d Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 26 Mar 2024 18:17:11 +0400 Subject: [PATCH 054/119] feat: add mean and witdth max value check in init and update --- src/LogNormal/LogNormal.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/LogNormal/LogNormal.sol b/src/LogNormal/LogNormal.sol index db032b4a..59bb18ce 100644 --- a/src/LogNormal/LogNormal.sol +++ b/src/LogNormal/LogNormal.sol @@ -83,6 +83,9 @@ contract LogNormal is PairStrategy { (reserves, totalLiquidity, params) = abi.decode(data, (uint256[], uint256, LogNormalParams)); + if (params.mean > MAX_MEAN) revert MeanTooLarge(); + if (params.width > MAX_WIDTH) revert WidthTooLarge(); + internalParams[poolId].mean.lastComputedValue = params.mean; internalParams[poolId].width.lastComputedValue = params.width; internalParams[poolId].swapFee = params.swapFee; @@ -108,10 +111,12 @@ contract LogNormal is PairStrategy { } else if (updateCode == UpdateCode.Width) { (uint256 targetWidth, uint256 targetTimestamp) = decodeWidthUpdate(data); + if (targetWidth > MAX_WIDTH) revert WidthTooLarge(); internalParams[poolId].width.set(targetWidth, targetTimestamp); } else if (updateCode == UpdateCode.Mean) { (uint256 targetMean, uint256 targetTimestamp) = decodeMeanUpdate(data); + if (targetMean > MAX_MEAN) revert MeanTooLarge(); internalParams[poolId].mean.set(targetMean, targetTimestamp); } else if (updateCode == UpdateCode.Controller) { internalParams[poolId].controller = decodeControllerUpdate(data); From 99b01585058de75eb2fe6c32cba5019ab5d00544 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 26 Mar 2024 18:22:31 +0400 Subject: [PATCH 055/119] chore: remove mention of tau in LogNormalMath --- src/LogNormal/LogNormalMath.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LogNormal/LogNormalMath.sol b/src/LogNormal/LogNormalMath.sol index 5a5c9a08..bf4e1043 100644 --- a/src/LogNormal/LogNormalMath.sol +++ b/src/LogNormal/LogNormalMath.sol @@ -111,8 +111,8 @@ function computeD1( LogNormalParams memory params ) pure returns (int256 d1) { int256 lnSDivK = computeLnSDivK(S, params.mean); - uint256 halfSigmaPowTwoTau = computeHalfSigmaSquared(params.width); - d1 = (lnSDivK + int256(halfSigmaPowTwoTau)).wadDiv(int256(params.width)); + uint256 halfSigmaPowTwo = computeHalfSigmaSquared(params.width); + d1 = (lnSDivK + int256(halfSigmaPowTwo)).wadDiv(int256(params.width)); } /// @dev Computes the d2 parameter for the Black-Scholes formula. From ad69860265c06e79ca1840e6dff84dec1a6756b4 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 26 Mar 2024 18:24:28 +0400 Subject: [PATCH 056/119] chore: rename old K to mean --- src/LogNormal/LogNormalMath.sol | 15 +++++++++------ test/utils/LogNormalArbitrage.sol | 8 ++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/LogNormal/LogNormalMath.sol b/src/LogNormal/LogNormalMath.sol index bf4e1043..869452eb 100644 --- a/src/LogNormal/LogNormalMath.sol +++ b/src/LogNormal/LogNormalMath.sol @@ -42,8 +42,11 @@ function computeDeltaGivenDeltaLRoundDown( return reserve.mulWadDown(deltaLiquidity.divWadDown(totalLiquidity)); } -function computeLnSDivK(uint256 S, uint256 K) pure returns (int256 lnSDivK) { - lnSDivK = int256(S.divWadUp(K)).lnWad(); +function computeLnSDivMean( + uint256 S, + uint256 mean +) pure returns (int256 lnSDivK) { + lnSDivK = int256(S.divWadUp(mean)).lnWad(); } /** @@ -110,9 +113,9 @@ function computeD1( uint256 S, LogNormalParams memory params ) pure returns (int256 d1) { - int256 lnSDivK = computeLnSDivK(S, params.mean); + int256 lnSDivMean = computeLnSDivMean(S, params.mean); uint256 halfSigmaPowTwo = computeHalfSigmaSquared(params.width); - d1 = (lnSDivK + int256(halfSigmaPowTwo)).wadDiv(int256(params.width)); + d1 = (lnSDivMean + int256(halfSigmaPowTwo)).wadDiv(int256(params.width)); } /// @dev Computes the d2 parameter for the Black-Scholes formula. @@ -124,9 +127,9 @@ function computeD2( uint256 S, LogNormalParams memory params ) pure returns (int256 d2) { - int256 lnSDivK = computeLnSDivK(S, params.mean); + int256 lnSDivMean = computeLnSDivMean(S, params.mean); uint256 halfSigmaPowTwo = computeHalfSigmaSquared(params.width); - d2 = (lnSDivK - int256(halfSigmaPowTwo)).wadDiv(int256(params.width)); + d2 = (lnSDivMean - int256(halfSigmaPowTwo)).wadDiv(int256(params.width)); } /** diff --git a/test/utils/LogNormalArbitrage.sol b/test/utils/LogNormalArbitrage.sol index f7283127..811551a7 100644 --- a/test/utils/LogNormalArbitrage.sol +++ b/test/utils/LogNormalArbitrage.sol @@ -324,8 +324,8 @@ contract LogNormalArbitrage { int256 mean = int256(params.mean); int256 width = int256(params.width); - int256 lnSDivK = computeLnSDivK(uint256(S), params.mean); - int256 a = lnSDivK.wadDiv(width) - width.wadDiv(I_TWO); + int256 lnSDivMean = computeLnSDivMean(uint256(S), params.mean); + int256 a = lnSDivMean.wadDiv(width) - width.wadDiv(I_TWO); int256 cdfA = Gaussian.cdf(a); int256 delta = L.wadMul(mean).wadMul(cdfA); @@ -341,8 +341,8 @@ contract LogNormalArbitrage { int256 gamma = I_ONE - int256(params.swapFee); int256 width = int256(params.width); - int256 lnSDivK = computeLnSDivK(uint256(S), params.mean); - int256 a = Gaussian.cdf(lnSDivK.wadDiv(width) + width.wadDiv(I_TWO)); + int256 lnSDivMean = computeLnSDivMean(uint256(S), params.mean); + int256 a = Gaussian.cdf(lnSDivMean.wadDiv(width) + width.wadDiv(I_TWO)); int256 delta = L.wadMul(I_ONE - a); dx = delta - rX; From 3f94e0181724074e70c2b6134331053c113d2d22 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 13:52:53 +0400 Subject: [PATCH 057/119] fix: add _computeSwapDeltaLiquidity to PairStrategy --- src/PairStrategy.sol | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/PairStrategy.sol b/src/PairStrategy.sol index 5dcff55b..66947069 100644 --- a/src/PairStrategy.sol +++ b/src/PairStrategy.sol @@ -139,8 +139,12 @@ abstract contract PairStrategy is IStrategy { { bytes memory params = getPoolParams(poolId); - (tokenInIndex, tokenOutIndex, amountIn, amountOut, deltaLiquidity) = - abi.decode(data, (uint256, uint256, uint256, uint256, uint256)); + (tokenInIndex, tokenOutIndex, amountIn, amountOut) = + abi.decode(data, (uint256, uint256, uint256, uint256)); + + deltaLiquidity = _computeSwapDeltaLiquidity( + pool, tokenInIndex, tokenOutIndex, amountIn, amountOut + ); pool.reserves[tokenInIndex] += amountIn; pool.reserves[tokenOutIndex] -= amountOut; @@ -195,4 +199,15 @@ abstract contract PairStrategy is IStrategy { Pool memory pool, bytes memory data ) internal view virtual returns (uint256[] memory); + + /** + * @dev Computes the deltaLiquidity for a swap operation. + */ + function _computeSwapDeltaLiquidity( + Pool memory pool, + uint256 tokenInIndex, + uint256 tokenOutIndex, + uint256 amountIn, + uint256 amountOut + ) internal view virtual returns (uint256); } From dad2ff52149150c334a7db1e2881adfe0d13e449 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 14:05:59 +0400 Subject: [PATCH 058/119] feat: add computeSwapDeltaLiquidity to ConstantSumMath --- src/ConstantSum/ConstantSumMath.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ConstantSum/ConstantSumMath.sol b/src/ConstantSum/ConstantSumMath.sol index 5e58f9e2..55cc93e8 100644 --- a/src/ConstantSum/ConstantSumMath.sol +++ b/src/ConstantSum/ConstantSumMath.sol @@ -98,3 +98,15 @@ function computeDeltaGivenDeltaLRoundDown( ) pure returns (uint256) { return reserve.mulWadDown(deltaLiquidity.divWadDown(totalLiquidity)); } + +function computeSwapDeltaLiquidity( + uint256 delta, + ConstantSumParams memory params, + bool isSwapXForY +) pure returns (uint256) { + if (isSwapXForY) { + return (ONE - params.swapFee).mulWadDown(delta); + } else { + return (ONE - params.swapFee).mulWadDown(params.price).mulWadDown(delta); + } +} From 869f4823cfc98ee22dfc8f206e579ec9e1c912fd Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 14:08:21 +0400 Subject: [PATCH 059/119] feat: add _computeSwapDeltaLiquidity to ConstantSum --- src/ConstantSum/ConstantSum.sol | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index 08a1c27d..4efc7538 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -2,7 +2,9 @@ pragma solidity 0.8.22; import { - FixedPointMathLib, computeTradingFunction + FixedPointMathLib, + computeTradingFunction, + computeSwapDeltaLiquidity } from "./ConstantSumMath.sol"; import { decodePriceUpdate, @@ -139,4 +141,22 @@ contract ConstantSum is PairStrategy { ) internal pure override returns (uint256[] memory) { return new uint256[](0); } + + function _computeSwapDeltaLiquidity( + Pool memory, + bytes memory params, + uint256 tokenInIndex, + uint256, + uint256 amountIn, + uint256 + ) internal pure override returns (uint256) { + if (tokenInIndex == 0) { + return computeSwapDeltaLiquidity( + amountIn, abi.decode(params, (ConstantSumParams)), true + ); + } + return computeSwapDeltaLiquidity( + amountIn, abi.decode(params, (ConstantSumParams)), false + ); + } } From bda406559bd385042c18bf750078b3edb0d414c0 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 14:08:34 +0400 Subject: [PATCH 060/119] feat: update _computeSwapDeltaLiquidity in PairStrategy --- src/PairStrategy.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PairStrategy.sol b/src/PairStrategy.sol index 66947069..3b371281 100644 --- a/src/PairStrategy.sol +++ b/src/PairStrategy.sol @@ -143,7 +143,7 @@ abstract contract PairStrategy is IStrategy { abi.decode(data, (uint256, uint256, uint256, uint256)); deltaLiquidity = _computeSwapDeltaLiquidity( - pool, tokenInIndex, tokenOutIndex, amountIn, amountOut + pool, params, tokenInIndex, tokenOutIndex, amountIn, amountOut ); pool.reserves[tokenInIndex] += amountIn; @@ -205,6 +205,7 @@ abstract contract PairStrategy is IStrategy { */ function _computeSwapDeltaLiquidity( Pool memory pool, + bytes memory params, uint256 tokenInIndex, uint256 tokenOutIndex, uint256 amountIn, From e591dea1496f58f89269d3e243b0cf872a22fff6 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 14:09:00 +0400 Subject: [PATCH 061/119] chore: add missing NatSpec to _computeSwapDeltaLiquidity --- src/ConstantSum/ConstantSum.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index 4efc7538..24ff771f 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -142,6 +142,7 @@ contract ConstantSum is PairStrategy { return new uint256[](0); } + /// @inheritdoc PairStrategy function _computeSwapDeltaLiquidity( Pool memory, bytes memory params, From 28b310c45e420bea118dcf5d3daa5164a330bd9c Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 14:19:47 +0400 Subject: [PATCH 062/119] feat: add computeSwapDeltaLiquidity to G3MMath --- src/GeometricMean/G3MMath.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/GeometricMean/G3MMath.sol b/src/GeometricMean/G3MMath.sol index 0e409569..218b4d36 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -254,3 +254,15 @@ function computeInitialPoolData( return abi.encode(amountX, rY, L, params.wX, params.swapFee, params.controller); } + +function computeSwapDeltaLiquidity( + uint256 amountIn, + uint256 reserve, + uint256 totalLiquidity, + uint256 weight, + uint256 swapFee +) pure returns (uint256) { + return weight.mulWadUp(ONE - swapFee).mulWadUp(totalLiquidity).mulWadUp( + amountIn.divWadUp(reserve) + ); +} From c9c6f537dc249631574aee6cd1d63284cfc4193f Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 14:22:10 +0400 Subject: [PATCH 063/119] feat: add _computeSwapDeltaLiquidity to G3M --- src/GeometricMean/GeometricMean.sol | 34 ++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/GeometricMean/GeometricMean.sol b/src/GeometricMean/GeometricMean.sol index 1dcf1942..dcb6287e 100644 --- a/src/GeometricMean/GeometricMean.sol +++ b/src/GeometricMean/GeometricMean.sol @@ -8,7 +8,8 @@ import { Pool } from "src/interfaces/IDFMM.sol"; import { computeTradingFunction, computeDeltaGivenDeltaLRoundUp, - computeDeltaGivenDeltaLRoundDown + computeDeltaGivenDeltaLRoundDown, + computeSwapDeltaLiquidity } from "./G3MMath.sol"; import { ONE } from "src/lib/StrategyLib.sol"; @@ -200,4 +201,35 @@ contract GeometricMean is PairStrategy { pool.reserves[1], deltaLiquidity, pool.totalLiquidity ); } + + /// @inheritdoc PairStrategy + function _computeSwapDeltaLiquidity( + Pool memory pool, + bytes memory params, + uint256 tokenInIndex, + uint256, + uint256 amountIn, + uint256 + ) internal pure override returns (uint256) { + GeometricMeanParams memory poolParams = + abi.decode(params, (GeometricMeanParams)); + + if (tokenInIndex == 0) { + return computeSwapDeltaLiquidity( + amountIn, + pool.reserves[0], + pool.totalLiquidity, + poolParams.wX, + poolParams.swapFee + ); + } + + return computeSwapDeltaLiquidity( + amountIn, + pool.reserves[1], + pool.totalLiquidity, + poolParams.wY, + poolParams.swapFee + ); + } } From b707a67d0272c4d101e322df778b84acb37c41a9 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 14:29:26 +0400 Subject: [PATCH 064/119] feat: add computeSwapXForYDeltaLiquidity to LogNormalMath --- src/LogNormal/LogNormalMath.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/LogNormal/LogNormalMath.sol b/src/LogNormal/LogNormalMath.sol index 5a5c9a08..d910cd90 100644 --- a/src/LogNormal/LogNormalMath.sol +++ b/src/LogNormal/LogNormalMath.sol @@ -377,3 +377,16 @@ function computeNextRy( rY = upperInput; } } + +function computeSwapXForYDeltaLiquidity( + uint256 deltaX, + uint256 reserveX, + uint256 reserveY, + uint256 totalLiquidity, + uint256 price, + uint256 swapFee +) pure returns (uint256) { + uint256 a = price.divWadUp(price.mulWadUp(reserveX) + reserveY); + uint256 b = (ONE - swapFee).mulWadUp(deltaX).divWadUp(reserveX); + return a.mulWadUp(totalLiquidity).mulWadUp(b); +} From e05bcb8eac516a8c96823c2e50af95a36a1da5f3 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 14:31:35 +0400 Subject: [PATCH 065/119] feat: add computeSwapYForXDeltaLiquidity in LogNormalMath --- src/LogNormal/LogNormalMath.sol | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/LogNormal/LogNormalMath.sol b/src/LogNormal/LogNormalMath.sol index d910cd90..4fdff53b 100644 --- a/src/LogNormal/LogNormalMath.sol +++ b/src/LogNormal/LogNormalMath.sol @@ -390,3 +390,18 @@ function computeSwapXForYDeltaLiquidity( uint256 b = (ONE - swapFee).mulWadUp(deltaX).divWadUp(reserveX); return a.mulWadUp(totalLiquidity).mulWadUp(b); } + +function computeSwapYForXDeltaLiquidity( + uint256 deltaY, + uint256 reserveX, + uint256 reserveY, + uint256 totalLiquidity, + uint256 price, + uint256 swapFee +) pure returns (uint256) { + return totalLiquidity.mulWadUp( + (ONE - swapFee).mulWadUp(deltaY).divWadUp( + price.mulWadDown(reserveX) + reserveY + ) + ); +} From 4d4b108ab1c61378a924eff58762ec8799379c78 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 16:45:25 +0400 Subject: [PATCH 066/119] feat: add wip _computeSwapDeltaLiquidity to LogNormal --- src/LogNormal/LogNormal.sol | 38 ++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/LogNormal/LogNormal.sol b/src/LogNormal/LogNormal.sol index 1bcd5513..7c1366c4 100644 --- a/src/LogNormal/LogNormal.sol +++ b/src/LogNormal/LogNormal.sol @@ -7,7 +7,9 @@ import { DynamicParamLib, DynamicParam } from "src/lib/DynamicParamLib.sol"; import { computeTradingFunction, computeDeltaGivenDeltaLRoundUp, - computeDeltaGivenDeltaLRoundDown + computeDeltaGivenDeltaLRoundDown, + computeSwapXForYDeltaLiquidity, + computeSwapYForXDeltaLiquidity } from "src/LogNormal/LogNormalMath.sol"; import { decodeFeeUpdate, @@ -177,4 +179,38 @@ contract LogNormal is PairStrategy { ); return deltas; } + + function _computeSwapDeltaLiquidity( + Pool memory pool, + bytes memory params, + uint256 tokenInIndex, + uint256, + uint256 amountIn, + uint256 + ) internal pure override returns (uint256) { + // TODO: Compute the price. + uint256 price; + + LogNormalParams memory poolParams = + abi.decode(params, (LogNormalParams)); + if (tokenInIndex == 0) { + return computeSwapXForYDeltaLiquidity( + amountIn, + pool.reserves[0], + pool.reserves[1], + pool.totalLiquidity, + price, + poolParams.swapFee + ); + } + + return computeSwapYForXDeltaLiquidity( + amountIn, + pool.reserves[0], + pool.reserves[1], + pool.totalLiquidity, + price, + poolParams.swapFee + ); + } } From 8c8ce08d84a8831af4198eb9b7ed5c2f322b0f27 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 17:55:26 +0400 Subject: [PATCH 067/119] feat: compute price in LogNormal _computeSwapDeltaLiquidity --- src/LogNormal/LogNormal.sol | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/LogNormal/LogNormal.sol b/src/LogNormal/LogNormal.sol index 7c1366c4..015d86d6 100644 --- a/src/LogNormal/LogNormal.sol +++ b/src/LogNormal/LogNormal.sol @@ -9,7 +9,8 @@ import { computeDeltaGivenDeltaLRoundUp, computeDeltaGivenDeltaLRoundDown, computeSwapXForYDeltaLiquidity, - computeSwapYForXDeltaLiquidity + computeSwapYForXDeltaLiquidity, + computePriceGivenX } from "src/LogNormal/LogNormalMath.sol"; import { decodeFeeUpdate, @@ -188,11 +189,13 @@ contract LogNormal is PairStrategy { uint256 amountIn, uint256 ) internal pure override returns (uint256) { - // TODO: Compute the price. - uint256 price; - LogNormalParams memory poolParams = abi.decode(params, (LogNormalParams)); + + uint256 price = computePriceGivenX( + pool.reserves[0], pool.totalLiquidity, poolParams + ); + if (tokenInIndex == 0) { return computeSwapXForYDeltaLiquidity( amountIn, From 9562053989fc32a855c2a69a1d36206529adc95e Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 17:58:19 +0400 Subject: [PATCH 068/119] feat: add _computeSwapDeltaLiquidity to NTokenStrategy --- src/NTokenStrategy.sol | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/NTokenStrategy.sol b/src/NTokenStrategy.sol index 8f2f21d4..3e4e89cc 100644 --- a/src/NTokenStrategy.sol +++ b/src/NTokenStrategy.sol @@ -119,16 +119,20 @@ abstract contract NTokenStrategy is IStrategy { uint256 deltaLiquidity ) { - (tokenInIndex, tokenOutIndex, amountIn, amountOut, deltaLiquidity) = - abi.decode(data, (uint256, uint256, uint256, uint256, uint256)); + bytes memory params = getPoolParams(poolId); + + (tokenInIndex, tokenOutIndex, amountIn, amountOut) = + abi.decode(data, (uint256, uint256, uint256, uint256)); + + deltaLiquidity = _computeSwapDeltaLiquidity( + pool, params, tokenInIndex, tokenOutIndex, amountIn, amountOut + ); pool.reserves[tokenInIndex] += amountIn; pool.reserves[tokenOutIndex] -= amountOut; invariant = tradingFunction( - pool.reserves, - pool.totalLiquidity + deltaLiquidity, - getPoolParams(poolId) + pool.reserves, pool.totalLiquidity + deltaLiquidity, params ); valid = invariant >= 0; @@ -185,4 +189,16 @@ abstract contract NTokenStrategy is IStrategy { view virtual returns (uint256[] memory deltas, uint256[] memory nextReserves); + + /** + * @dev Computes the deltaLiquidity for a swap operation. + */ + function _computeSwapDeltaLiquidity( + Pool memory pool, + bytes memory params, + uint256 tokenInIndex, + uint256 tokenOutIndex, + uint256 amountIn, + uint256 amountOut + ) internal view virtual returns (uint256); } From 74912bcae32fb417061306732b830c0e258ae227 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 18:05:50 +0400 Subject: [PATCH 069/119] feat: add _computeSwapDeltaLiquidity to NTokenGeometricMean --- .../NTokenGeometricMean.sol | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/NTokenGeometricMean/NTokenGeometricMean.sol b/src/NTokenGeometricMean/NTokenGeometricMean.sol index be7e88d4..14dffa28 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMean.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMean.sol @@ -12,7 +12,8 @@ import { import { computeTradingFunction, computeDeltaGivenDeltaLRoundUp, - computeDeltaGivenDeltaLRoundDown + computeDeltaGivenDeltaLRoundDown, + computeSwapDeltaLiquidity } from "src/NTokenGeometricMean/NTokenGeometricMeanMath.sol"; import { ONE } from "src/lib/StrategyLib.sol"; @@ -250,4 +251,25 @@ contract NTokenGeometricMean is NTokenStrategy { nextReserves[i] = reserveT - deltas[i]; } } + + /// @inheritdoc NTokenStrategy + function _computeSwapDeltaLiquidity( + Pool memory pool, + bytes memory params, + uint256 tokenInIndex, + uint256, + uint256 amountIn, + uint256 + ) internal pure override returns (uint256) { + NTokenGeometricMeanParams memory poolParams = + abi.decode(params, (NTokenGeometricMeanParams)); + + return computeSwapDeltaLiquidity( + amountIn, + pool.reserves[tokenInIndex], + pool.totalLiquidity, + poolParams.weights[tokenInIndex], + poolParams.swapFee + ); + } } From 6a3c914cb31e5785c703288c4b3598dda8973bc0 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 18:06:05 +0400 Subject: [PATCH 070/119] feat: add computeSwapDeltaLiquidity to NTokenG3MMath --- src/NTokenGeometricMean/NTokenGeometricMeanMath.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol b/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol index cc69b4a3..44857e45 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol @@ -126,3 +126,15 @@ function computeNextLiquidity( } return accumulator; } + +function computeSwapDeltaLiquidity( + uint256 amountIn, + uint256 reserve, + uint256 totalLiquidity, + uint256 weight, + uint256 swapFee +) pure returns (uint256) { + return weight.mulWadUp(ONE - swapFee).mulWadUp(totalLiquidity).mulWadUp( + amountIn.divWadUp(reserve) + ); +} From a6a9ae3b1ad956c2132b85ad1a0d0421113fd511 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 18:15:27 +0400 Subject: [PATCH 071/119] feat: remove deltaLiquidity from simulateSwap in LogNormalSolver --- src/LogNormal/LogNormalSolver.sol | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/LogNormal/LogNormalSolver.sol b/src/LogNormal/LogNormalSolver.sol index 49f4d408..9266fc85 100644 --- a/src/LogNormal/LogNormalSolver.sol +++ b/src/LogNormal/LogNormalSolver.sol @@ -310,13 +310,9 @@ contract LogNormalSolver { bytes memory swapData; if (swapXIn) { - swapData = abi.encode( - 0, 1, amountIn, state.amountOut, state.deltaLiquidity - ); + swapData = abi.encode(0, 1, amountIn, state.amountOut); } else { - swapData = abi.encode( - 1, 0, amountIn, state.amountOut, state.deltaLiquidity - ); + swapData = abi.encode(1, 0, amountIn, state.amountOut); } uint256 poolId = poolId; From 2bec4ff52b1c47f274f2db8b1d5b09e2a221e6f4 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 18:16:02 +0400 Subject: [PATCH 072/119] feat: use same functions to compute swap deltaLiquidity in LogNormal strategy and solver --- src/LogNormal/LogNormal.sol | 19 ++++++------------- src/LogNormal/LogNormalMath.sol | 28 ---------------------------- 2 files changed, 6 insertions(+), 41 deletions(-) diff --git a/src/LogNormal/LogNormal.sol b/src/LogNormal/LogNormal.sol index 015d86d6..edcf86e6 100644 --- a/src/LogNormal/LogNormal.sol +++ b/src/LogNormal/LogNormal.sol @@ -8,9 +8,8 @@ import { computeTradingFunction, computeDeltaGivenDeltaLRoundUp, computeDeltaGivenDeltaLRoundDown, - computeSwapXForYDeltaLiquidity, - computeSwapYForXDeltaLiquidity, - computePriceGivenX + computeDeltaLXIn, + computeDeltaLYIn } from "src/LogNormal/LogNormalMath.sol"; import { decodeFeeUpdate, @@ -192,28 +191,22 @@ contract LogNormal is PairStrategy { LogNormalParams memory poolParams = abi.decode(params, (LogNormalParams)); - uint256 price = computePriceGivenX( - pool.reserves[0], pool.totalLiquidity, poolParams - ); - if (tokenInIndex == 0) { - return computeSwapXForYDeltaLiquidity( + return computeDeltaLXIn( amountIn, pool.reserves[0], pool.reserves[1], pool.totalLiquidity, - price, - poolParams.swapFee + poolParams ); } - return computeSwapYForXDeltaLiquidity( + return computeDeltaLYIn( amountIn, pool.reserves[0], pool.reserves[1], pool.totalLiquidity, - price, - poolParams.swapFee + poolParams ); } } diff --git a/src/LogNormal/LogNormalMath.sol b/src/LogNormal/LogNormalMath.sol index 4fdff53b..5a5c9a08 100644 --- a/src/LogNormal/LogNormalMath.sol +++ b/src/LogNormal/LogNormalMath.sol @@ -377,31 +377,3 @@ function computeNextRy( rY = upperInput; } } - -function computeSwapXForYDeltaLiquidity( - uint256 deltaX, - uint256 reserveX, - uint256 reserveY, - uint256 totalLiquidity, - uint256 price, - uint256 swapFee -) pure returns (uint256) { - uint256 a = price.divWadUp(price.mulWadUp(reserveX) + reserveY); - uint256 b = (ONE - swapFee).mulWadUp(deltaX).divWadUp(reserveX); - return a.mulWadUp(totalLiquidity).mulWadUp(b); -} - -function computeSwapYForXDeltaLiquidity( - uint256 deltaY, - uint256 reserveX, - uint256 reserveY, - uint256 totalLiquidity, - uint256 price, - uint256 swapFee -) pure returns (uint256) { - return totalLiquidity.mulWadUp( - (ONE - swapFee).mulWadUp(deltaY).divWadUp( - price.mulWadDown(reserveX) + reserveY - ) - ); -} From 166cbaf05eb09f812061135991d0be868c4bbf10 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 19:06:13 +0400 Subject: [PATCH 073/119] feat: fix computeSwapDeltaLiquidity formula in G3MMath --- src/GeometricMean/G3MMath.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GeometricMean/G3MMath.sol b/src/GeometricMean/G3MMath.sol index 218b4d36..4ddbc0e8 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -262,7 +262,7 @@ function computeSwapDeltaLiquidity( uint256 weight, uint256 swapFee ) pure returns (uint256) { - return weight.mulWadUp(ONE - swapFee).mulWadUp(totalLiquidity).mulWadUp( + return weight.mulWadUp(swapFee).mulWadUp(totalLiquidity).mulWadUp( amountIn.divWadUp(reserve) ); } From a984a6dfddb0cfaead8d54e18ca6cf64b2f28ffb Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 19:06:42 +0400 Subject: [PATCH 074/119] feat: remove deltaLiquidity parameter in simulateSwap from ConstantSumSolver --- src/ConstantSum/ConstantSumSolver.sol | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ConstantSum/ConstantSumSolver.sol b/src/ConstantSum/ConstantSumSolver.sol index 1144d382..a286567d 100644 --- a/src/ConstantSum/ConstantSumSolver.sol +++ b/src/ConstantSum/ConstantSumSolver.sol @@ -81,13 +81,9 @@ contract ConstantSumSolver { bytes memory swapData; if (swapXIn) { - swapData = abi.encode( - 0, 1, amountIn, state.amountOut, state.deltaLiquidity - ); + swapData = abi.encode(0, 1, amountIn, state.amountOut); } else { - swapData = abi.encode( - 1, 0, amountIn, state.amountOut, state.deltaLiquidity - ); + swapData = abi.encode(1, 0, amountIn, state.amountOut); } (bool valid,,,,,,) = IStrategy(strategy).validateSwap( From ba12fb21d8c934469735d32a4b7b9dc951c595b3 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 19:06:52 +0400 Subject: [PATCH 075/119] feat: remove deltaLiquidity parameter in simulateSwap from G3MSolver --- src/GeometricMean/GeometricMeanSolver.sol | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/GeometricMean/GeometricMeanSolver.sol b/src/GeometricMean/GeometricMeanSolver.sol index 6cf9d359..4e4aeb0d 100644 --- a/src/GeometricMean/GeometricMeanSolver.sol +++ b/src/GeometricMean/GeometricMeanSolver.sol @@ -211,13 +211,8 @@ contract GeometricMeanSolver { state.amountOut = state.outReserve - a; } - bytes memory swapData = abi.encode( - tokenInIndex, - tokenOutIndex, - amountIn, - state.amountOut, - state.deltaLiquidity - ); + bytes memory swapData = + abi.encode(tokenInIndex, tokenOutIndex, amountIn, state.amountOut); (bool valid,,,,,,) = IStrategy(strategy).validateSwap( address(this), poolId, pool, swapData From 85a16952ed69cde04cefeea200914b6f72810523 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 19:07:02 +0400 Subject: [PATCH 076/119] feat: remove deltaLiquidity parameter in simulateSwap from NTokenG3MSolver --- src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol b/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol index e254d7a4..ba4de9f7 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol @@ -111,13 +111,8 @@ contract NTokenGeometricMeanSolver { state.amountOut = state.outReserve - a; } - bytes memory swapData = abi.encode( - tokenInIndex, - tokenOutIndex, - amountIn, - state.amountOut, - state.deltaLiquidity - ); + bytes memory swapData = + abi.encode(tokenInIndex, tokenOutIndex, amountIn, state.amountOut); (bool valid,,,,,,) = IStrategy(strategy).validateSwap( address(this), poolId, pool, swapData From fc63b45715ef474d98722e9e5929113e9e3f42eb Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 27 Mar 2024 19:08:41 +0400 Subject: [PATCH 077/119] feat: fix computeSwapDeltaLiquidity in ConstantSumMath --- src/ConstantSum/ConstantSumMath.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ConstantSum/ConstantSumMath.sol b/src/ConstantSum/ConstantSumMath.sol index 55cc93e8..19cfcadd 100644 --- a/src/ConstantSum/ConstantSumMath.sol +++ b/src/ConstantSum/ConstantSumMath.sol @@ -105,8 +105,8 @@ function computeSwapDeltaLiquidity( bool isSwapXForY ) pure returns (uint256) { if (isSwapXForY) { - return (ONE - params.swapFee).mulWadDown(delta); + return (params.swapFee).mulWadDown(delta); } else { - return (ONE - params.swapFee).mulWadDown(params.price).mulWadDown(delta); + return (params.swapFee).mulWadDown(params.price).mulWadDown(delta); } } From fdadbbd9b5525d65a61992d36b1bdd1e2c9eb85a Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 28 Mar 2024 16:55:56 +0400 Subject: [PATCH 078/119] fix: ConstantSum computeSwapDeltaLiquidity --- src/ConstantSum/ConstantSumMath.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ConstantSum/ConstantSumMath.sol b/src/ConstantSum/ConstantSumMath.sol index 92ed3722..2800c40f 100644 --- a/src/ConstantSum/ConstantSumMath.sol +++ b/src/ConstantSum/ConstantSumMath.sol @@ -47,8 +47,8 @@ function computeSwapDeltaLiquidity( bool isSwapXForY ) pure returns (uint256) { if (isSwapXForY) { - return (params.swapFee).mulWadDown(delta); + return (params.swapFee).mulWadUp(delta); } else { - return (params.swapFee).mulWadDown(params.price).mulWadDown(delta); + return (params.swapFee).divWadDown(params.price).mulWadDown(delta); } } From 46c1027d1f72bee0ca83abb3c4e62689cb7fa98e Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 10:39:53 +0400 Subject: [PATCH 079/119] feat: simplify _computeSwapDeltaLiquidity in ConstantSum --- src/ConstantSum/ConstantSum.sol | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index 50b573a8..c50232a8 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -239,13 +239,8 @@ contract ConstantSum is PairStrategy { uint256 amountIn, uint256 ) internal pure override returns (uint256) { - if (tokenInIndex == 0) { - return computeSwapDeltaLiquidity( - amountIn, abi.decode(params, (ConstantSumParams)), true - ); - } return computeSwapDeltaLiquidity( - amountIn, abi.decode(params, (ConstantSumParams)), false + amountIn, abi.decode(params, (ConstantSumParams)), tokenInIndex == 0 ); } } From 7699e52cb4ddeb1eb3080b69bfd70a3f7ff51913 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 10:43:09 +0400 Subject: [PATCH 080/119] feat: use mulDiv in computeSwapDeltaLiquidity for ConstantSum --- src/ConstantSum/ConstantSumMath.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConstantSum/ConstantSumMath.sol b/src/ConstantSum/ConstantSumMath.sol index 2800c40f..a95ba4b2 100644 --- a/src/ConstantSum/ConstantSumMath.sol +++ b/src/ConstantSum/ConstantSumMath.sol @@ -49,6 +49,6 @@ function computeSwapDeltaLiquidity( if (isSwapXForY) { return (params.swapFee).mulWadUp(delta); } else { - return (params.swapFee).divWadDown(params.price).mulWadDown(delta); + return (params.swapFee).mulDivUp(delta, params.price); } } From 6f12c05a1afd56ca455ca65922390bdcc6ca61ca Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 10:43:18 +0400 Subject: [PATCH 081/119] feat: use computeSwapDeltaLiquidity in ConstantSumSolver --- src/ConstantSum/ConstantSumSolver.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ConstantSum/ConstantSumSolver.sol b/src/ConstantSum/ConstantSumSolver.sol index a286567d..95ca306e 100644 --- a/src/ConstantSum/ConstantSumSolver.sol +++ b/src/ConstantSum/ConstantSumSolver.sol @@ -12,7 +12,8 @@ import { import { ONE, computeInitialPoolData, - FixedPointMathLib + FixedPointMathLib, + computeSwapDeltaLiquidity } from "./ConstantSumMath.sol"; contract ConstantSumSolver { @@ -59,7 +60,7 @@ contract ConstantSumSolver { SimulateSwapState memory state; if (swapXIn) { - state.deltaLiquidity = amountIn.mulWadUp(poolParams.swapFee); + computeSwapDeltaLiquidity(amountIn, poolParams, true); state.amountOut = amountIn.mulWadDown(poolParams.price).mulWadDown( ONE - poolParams.swapFee ); @@ -68,8 +69,7 @@ contract ConstantSumSolver { revert NotEnoughLiquidity(); } } else { - state.deltaLiquidity = - amountIn.mulWadUp(poolParams.swapFee).divWadUp(poolParams.price); + computeSwapDeltaLiquidity(amountIn, poolParams, false); state.amountOut = (ONE - poolParams.swapFee).mulWadDown(amountIn) .divWadDown(poolParams.price); From f182bac73ca00d18a97d4e6de5d447d0b96fe46e Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 10:55:44 +0400 Subject: [PATCH 082/119] feat: round up pool controller swap fees in DFMM swap --- src/DFMM.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index cf4e12bb..17d886ad 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -239,7 +239,7 @@ contract DFMM is IDFMM { if (_pools[poolId].feeCollector != address(0)) { uint256 fees = - state.deltaLiquidity.mulWadDown(_pools[poolId].controllerFee); + state.deltaLiquidity.mulWadUp(_pools[poolId].controllerFee); _pools[poolId].totalLiquidity += state.deltaLiquidity - fees; _manageTokens(_pools[poolId].feeCollector, poolId, true, fees); _pools[poolId].totalLiquidity += fees; From 1a2a4b4917f387cceee65258db48dd835187acfa Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 10:59:36 +0400 Subject: [PATCH 083/119] feat: check controller fee instead of controller address in DFMM swap --- src/DFMM.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index 17d886ad..aea038c7 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -237,7 +237,7 @@ contract DFMM is IDFMM { if (!state.valid) revert InvalidInvariant(state.invariant); - if (_pools[poolId].feeCollector != address(0)) { + if (_pools[poolId].controllerFee > 0) { uint256 fees = state.deltaLiquidity.mulWadUp(_pools[poolId].controllerFee); _pools[poolId].totalLiquidity += state.deltaLiquidity - fees; From dce5f77ff525d5a4dff60ef7135ebfeef4d97ad1 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 11:22:53 +0400 Subject: [PATCH 084/119] feat: add min bounds for LogNormal width and mean --- src/LogNormal/LogNormal.sol | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/LogNormal/LogNormal.sol b/src/LogNormal/LogNormal.sol index 59bb18ce..8891dc2b 100644 --- a/src/LogNormal/LogNormal.sol +++ b/src/LogNormal/LogNormal.sol @@ -39,13 +39,15 @@ struct LogNormalParams { address controller; } -/// @dev Thrown when the mean paramter is larger than the maximum. -error MeanTooLarge(); +/// @dev Thrown when the mean parameter is not within the allowed bounds. +error InvalidMean(); -/// @dev Thrown when the width paramter is larger than the maximum. -error WidthTooLarge(); +/// @dev Thrown when the width parameter is not within the allowed bounds. +error InvalidWidth(); +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); /** @@ -83,8 +85,12 @@ contract LogNormal is PairStrategy { (reserves, totalLiquidity, params) = abi.decode(data, (uint256[], uint256, LogNormalParams)); - if (params.mean > MAX_MEAN) revert MeanTooLarge(); - if (params.width > MAX_WIDTH) revert WidthTooLarge(); + if (params.mean < MIN_WIDTH || params.mean > MAX_MEAN) { + revert InvalidMean(); + } + if (params.width < MIN_WIDTH || params.width > MAX_WIDTH) { + revert InvalidWidth(); + } internalParams[poolId].mean.lastComputedValue = params.mean; internalParams[poolId].width.lastComputedValue = params.width; @@ -111,12 +117,16 @@ contract LogNormal is PairStrategy { } else if (updateCode == UpdateCode.Width) { (uint256 targetWidth, uint256 targetTimestamp) = decodeWidthUpdate(data); - if (targetWidth > MAX_WIDTH) revert WidthTooLarge(); + if (targetWidth < MIN_WIDTH || targetWidth > MAX_WIDTH) { + revert InvalidWidth(); + } internalParams[poolId].width.set(targetWidth, targetTimestamp); } else if (updateCode == UpdateCode.Mean) { (uint256 targetMean, uint256 targetTimestamp) = decodeMeanUpdate(data); - if (targetMean > MAX_MEAN) revert MeanTooLarge(); + if (targetMean < MIN_MEAN || targetMean > MAX_MEAN) { + revert InvalidMean(); + } internalParams[poolId].mean.set(targetMean, targetTimestamp); } else if (updateCode == UpdateCode.Controller) { internalParams[poolId].controller = decodeControllerUpdate(data); From 78ddd8c7043fb93673d5a8c8e5dbd266d3234586 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 11:44:45 +0400 Subject: [PATCH 085/119] fix: add missing closing bracket in LogNormal --- src/LogNormal/LogNormal.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/LogNormal/LogNormal.sol b/src/LogNormal/LogNormal.sol index cc17ce55..4930f6ae 100644 --- a/src/LogNormal/LogNormal.sol +++ b/src/LogNormal/LogNormal.sol @@ -88,9 +88,10 @@ contract LogNormal is PairStrategy { if (params.mean < MIN_WIDTH || params.mean > MAX_MEAN) { revert InvalidMean(); } - + if (params.width < MIN_WIDTH || params.width > MAX_WIDTH) { revert InvalidWidth(); + } if (pool.reserves.length != 2 || reserves.length != 2) { revert InvalidReservesLength(); From 744e716cbb8528b5ccf55ef5f97508b1a526f476 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 14:44:45 +0400 Subject: [PATCH 086/119] fix: remove useless return in decodeFeeUpdate in ConstantSumUtils --- src/ConstantSum/ConstantSumUtils.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ConstantSum/ConstantSumUtils.sol b/src/ConstantSum/ConstantSumUtils.sol index 12064357..71b98d25 100644 --- a/src/ConstantSum/ConstantSumUtils.sol +++ b/src/ConstantSum/ConstantSumUtils.sol @@ -20,7 +20,6 @@ function encodeControllerUpdate(address controller) function decodeFeeUpdate(bytes memory data) pure returns (uint256 swapFee) { (, swapFee) = abi.decode(data, (UpdateCode, uint256)); - return swapFee; } function decodePriceUpdate(bytes memory data) From da8d520ce0336c1b05cd369ce950c1a24519d222 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 14:45:37 +0400 Subject: [PATCH 087/119] fix: remove useless library using in ConstantSumMath --- src/ConstantSum/ConstantSumMath.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ConstantSum/ConstantSumMath.sol b/src/ConstantSum/ConstantSumMath.sol index 5e58f9e2..51f8c203 100644 --- a/src/ConstantSum/ConstantSumMath.sol +++ b/src/ConstantSum/ConstantSumMath.sol @@ -6,7 +6,6 @@ import { ConstantSumParams } from "src/ConstantSum/ConstantSum.sol"; import { ONE } from "src/lib/StrategyLib.sol"; using FixedPointMathLib for uint256; -using FixedPointMathLib for int256; function computeTradingFunction( uint256[] memory reserves, From 802491bba184ec9b162c5722733c43c79e116b18 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 14:46:23 +0400 Subject: [PATCH 088/119] fix: remove useless library using in ConstantSumSolver --- src/ConstantSum/ConstantSumSolver.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ConstantSum/ConstantSumSolver.sol b/src/ConstantSum/ConstantSumSolver.sol index 1144d382..2645edaf 100644 --- a/src/ConstantSum/ConstantSumSolver.sol +++ b/src/ConstantSum/ConstantSumSolver.sol @@ -19,7 +19,6 @@ contract ConstantSumSolver { error NotEnoughLiquidity(); using FixedPointMathLib for uint256; - using FixedPointMathLib for int256; struct Reserves { uint256 rx; From 5c5200354c281acc0f1ef8505070f38126825cba Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 14:47:15 +0400 Subject: [PATCH 089/119] fix: remove unused wX parameter in InitState in NTokenG3M --- src/NTokenGeometricMean/NTokenGeometricMean.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NTokenGeometricMean/NTokenGeometricMean.sol b/src/NTokenGeometricMean/NTokenGeometricMean.sol index be7e88d4..f1e876c3 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMean.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMean.sol @@ -69,7 +69,6 @@ contract NTokenGeometricMean is NTokenStrategy { int256 invariant; address controller; uint256 swapFee; - uint256 wX; uint256 totalLiquidity; uint256[] reserves; uint256[] weights; From 28315ced69e73873a198a11489787669cde396ca Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 14:57:23 +0400 Subject: [PATCH 090/119] fix: move test constants in StrategyLib into test files --- src/lib/StrategyLib.sol | 3 --- test/utils/LogNormalArbitrage.sol | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/StrategyLib.sol b/src/lib/StrategyLib.sol index d1d60387..03f733ed 100644 --- a/src/lib/StrategyLib.sol +++ b/src/lib/StrategyLib.sol @@ -6,9 +6,6 @@ import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; uint256 constant HALF = 0.5e18; uint256 constant ONE = 1e18; uint256 constant TWO = 2e18; -int256 constant I_ONE = int256(ONE); -int256 constant I_TWO = int256(TWO); -int256 constant I_HALF = int256(HALF); using FixedPointMathLib for uint256; using FixedPointMathLib for int256; diff --git a/test/utils/LogNormalArbitrage.sol b/test/utils/LogNormalArbitrage.sol index f7283127..ebc66414 100644 --- a/test/utils/LogNormalArbitrage.sol +++ b/test/utils/LogNormalArbitrage.sol @@ -34,6 +34,10 @@ interface SolverLike { returns (LogNormalParams memory); } +int256 constant I_ONE = int256(ONE); +int256 constant I_TWO = int256(TWO); +int256 constant I_HALF = int256(HALF); + contract LogNormalArbitrage { using FixedPointMathLib for uint256; using FixedPointMathLib for int256; From a7cf0aac5db9be8ed1436c3f11e98956aedcae39 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 15:12:21 +0400 Subject: [PATCH 091/119] fix: add deltas array length check in NTokenStrategy --- src/NTokenStrategy.sol | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/NTokenStrategy.sol b/src/NTokenStrategy.sol index 3e4e89cc..281dab90 100644 --- a/src/NTokenStrategy.sol +++ b/src/NTokenStrategy.sol @@ -3,6 +3,12 @@ pragma solidity ^0.8.13; import { IStrategy, Pool } from "src/interfaces/IStrategy.sol"; +/** + * @dev Thrown when the length of the deltas array is not the + * same as the length of the reserves array. + */ +error InvalidTokenDeltas(); + /** * @title N-token strategy base contract for DFMM. * @notice This abstract contract defines the basic behavior of @@ -46,6 +52,9 @@ abstract contract NTokenStrategy is IStrategy { // we cannot assign to `deltaLiquidity` directly. (uint256[] memory maxTokenDeltas, uint256 deltaL) = abi.decode(data, (uint256[], uint256)); + if (maxTokenDeltas.length != pool.reserves.length) { + revert InvalidTokenDeltas(); + } deltaLiquidity = deltaL; (uint256[] memory deltas, uint256[] memory nextReserves) = @@ -82,8 +91,11 @@ abstract contract NTokenStrategy is IStrategy { { (uint256[] memory minTokenDeltas, uint256 deltaL) = abi.decode(data, (uint256[], uint256)); - + if (minTokenDeltas.length != pool.reserves.length) { + revert InvalidTokenDeltas(); + } deltaLiquidity = deltaL; + (uint256[] memory deltas, uint256[] memory nextReserves) = _computeDeallocateDeltasAndReservesGivenDeltaL( deltaLiquidity, minTokenDeltas, pool From 26f2f8c475a2a3d6bbad5f669e674b38e6359091 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 16:43:19 +0400 Subject: [PATCH 092/119] fix: use mulDiv for most operations in StrategyLib --- src/lib/StrategyLib.sol | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/lib/StrategyLib.sol b/src/lib/StrategyLib.sol index d1d60387..51d21fc1 100644 --- a/src/lib/StrategyLib.sol +++ b/src/lib/StrategyLib.sol @@ -19,8 +19,7 @@ function computeAllocationGivenX( uint256 rx, uint256 L ) pure returns (uint256 nextRx, uint256 nextL) { - uint256 liquidityPerRx = L.divWadUp(rx); - uint256 deltaL = amountX.mulWadUp(liquidityPerRx); + uint256 deltaL = amountX.mulDivDown(L, rx); nextRx = add ? rx + amountX : rx - amountX; nextL = add ? L + deltaL : L - deltaL; } @@ -31,8 +30,7 @@ function computeAllocationGivenY( uint256 ry, uint256 L ) pure returns (uint256 nextRy, uint256 nextL) { - uint256 liquidityPerRy = L.divWadUp(ry); - uint256 deltaL = amountY.mulWadUp(liquidityPerRy); + uint256 deltaL = amountY.mulDivDown(L, ry); nextRy = add ? ry + amountY : ry - amountY; nextL = add ? L + deltaL : L - deltaL; } @@ -42,7 +40,7 @@ function computeDeltaLGivenDeltaX( uint256 liquidity, uint256 reserveX ) pure returns (uint256 deltaL) { - return liquidity.mulWadDown(deltaX.divWadDown(reserveX)); + return liquidity.mulDivDown(deltaX, reserveX); } function computeDeltaLGivenDeltaY( @@ -50,7 +48,7 @@ function computeDeltaLGivenDeltaY( uint256 liquidity, uint256 reserveY ) pure returns (uint256 deltaL) { - return liquidity.mulWadDown(deltaY.divWadDown(reserveY)); + return liquidity.mulDivDown(deltaY, reserveY); } function computeDeltaYGivenDeltaX( @@ -58,7 +56,7 @@ function computeDeltaYGivenDeltaX( uint256 reserveX, uint256 reserveY ) pure returns (uint256 deltaY) { - return reserveY.mulWadDown(deltaX.divWadUp(reserveX)); + return reserveY.mulDivUp(deltaX, reserveX); } function computeDeltaXGivenDeltaL( @@ -66,7 +64,7 @@ function computeDeltaXGivenDeltaL( uint256 liquidity, uint256 reserveX ) pure returns (uint256 deltaX) { - return reserveX.mulWadUp(deltaL.divWadUp(liquidity)); + return reserveX.mulDivUp(deltaL, liquidity); } function computeDeltaYGivenDeltaL( @@ -74,5 +72,5 @@ function computeDeltaYGivenDeltaL( uint256 liquidity, uint256 reserveY ) pure returns (uint256 deltaX) { - return reserveY.mulWadUp(deltaL.divWadUp(liquidity)); + return reserveY.mulDivUp(deltaL, liquidity); } From 80bc05eedbc46dd6e4769a3b868485a8ad969343 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 17:15:54 +0400 Subject: [PATCH 093/119] fix: change rounding in G3M trading function --- src/GeometricMean/G3MMath.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GeometricMean/G3MMath.sol b/src/GeometricMean/G3MMath.sol index 0e409569..6f1a0ea3 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -15,8 +15,8 @@ function computeTradingFunction( uint256 L, GeometricMeanParams memory params ) pure returns (int256) { - uint256 a = uint256(int256(rX.divWadDown(L)).powWad(int256(params.wX))); - uint256 b = uint256(int256(rY.divWadDown(L)).powWad(int256(params.wY))); + uint256 a = uint256(int256(rX.divWadUp(L)).powWad(int256(params.wX))); + uint256 b = uint256(int256(rY.divWadUp(L)).powWad(int256(params.wY))); return int256(a.mulWadUp(b)) - int256(1 ether); } From d5b485d5d6d49172a645a7809c3d49f955adc2dc Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 17:42:33 +0400 Subject: [PATCH 094/119] fix: prevent weightX to be 0 --- src/GeometricMean/GeometricMean.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GeometricMean/GeometricMean.sol b/src/GeometricMean/GeometricMean.sol index 1dcf1942..9cad1c4f 100644 --- a/src/GeometricMean/GeometricMean.sol +++ b/src/GeometricMean/GeometricMean.sol @@ -92,7 +92,7 @@ contract GeometricMean is PairStrategy { data, (uint256, uint256, uint256, uint256, uint256, address) ); - if (state.wX >= ONE) { + if (state.wX == 0 || state.wX >= ONE) { revert InvalidWeightX(); } From d4b6fd2d6032d0615fee9fcc2ad13c5e0839758e Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 17:43:26 +0400 Subject: [PATCH 095/119] fix: add weightX check in G3M update --- src/GeometricMean/GeometricMean.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/GeometricMean/GeometricMean.sol b/src/GeometricMean/GeometricMean.sol index 9cad1c4f..25345902 100644 --- a/src/GeometricMean/GeometricMean.sol +++ b/src/GeometricMean/GeometricMean.sol @@ -129,6 +129,9 @@ contract GeometricMean is PairStrategy { } else if (updateCode == UpdateCode.WeightX) { (, uint256 targetWeightX, uint256 targetTimestamp) = abi.decode(data, (UpdateCode, uint256, uint256)); + if (targetWeightX == 0 || targetWeightX >= ONE) { + revert InvalidWeightX(); + } internalParams[poolId].wX.set(targetWeightX, targetTimestamp); } else if (updateCode == UpdateCode.Controller) { (, internalParams[poolId].controller) = From 6d2faab1421beda53f1c7c8dd667f5cce51529c0 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 17:53:56 +0400 Subject: [PATCH 096/119] fix: add missing onlyDFMM modifier in NTokenG3M --- src/NTokenGeometricMean/NTokenGeometricMean.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NTokenGeometricMean/NTokenGeometricMean.sol b/src/NTokenGeometricMean/NTokenGeometricMean.sol index be7e88d4..891fbd85 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMean.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMean.sol @@ -81,7 +81,7 @@ contract NTokenGeometricMean is NTokenStrategy { uint256 poolId, Pool calldata, bytes calldata data - ) external returns (bool, int256, uint256[] memory, uint256) { + ) external onlyDFMM returns (bool, int256, uint256[] memory, uint256) { InitState memory state; ( @@ -133,7 +133,7 @@ contract NTokenGeometricMean is NTokenStrategy { uint256 poolId, Pool calldata, bytes calldata data - ) external { + ) external onlyDFMM { if (sender != internalParams[poolId].controller) revert InvalidSender(); UpdateCode updateCode = abi.decode(data, (UpdateCode)); From 9605b1f7bfa395af97a77c688a292c98d9407575 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 18:01:53 +0400 Subject: [PATCH 097/119] fix: add remainder to dynamic parameter calculation --- src/lib/DynamicParamLib.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/DynamicParamLib.sol b/src/lib/DynamicParamLib.sol index 33a1c115..365ce3db 100644 --- a/src/lib/DynamicParamLib.sol +++ b/src/lib/DynamicParamLib.sol @@ -53,6 +53,15 @@ library DynamicParamLib { uint256 timeDelta = updateEnd - block.timestamp; int256 delta = int256(target) - int256(param.lastComputedValue); int256 deltaPerSecond = delta / int256(timeDelta); + + int256 remainder = delta % int256(timeDelta); + + if (remainder > 0) { + param.lastComputedValue += uint256(remainder); + } else { + param.lastComputedValue -= uint256(-remainder); + } + param.updateEnd = updateEnd; param.updatePerSecond = deltaPerSecond; } From 3c3b1ae44e1ad1f56bfa1ac79c83234ea69d8091 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 18:09:31 +0400 Subject: [PATCH 098/119] fix: remove totalLiquidity from init in ConstantSum --- src/ConstantSum/ConstantSum.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index 08a1c27d..4e7d0ba0 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -58,9 +58,8 @@ contract ConstantSum is PairStrategy { ) { ConstantSumParams memory params; - (reserves, totalLiquidity, params) = - abi.decode(data, (uint256[], uint256, ConstantSumParams)); - + (reserves, params) = abi.decode(data, (uint256[], ConstantSumParams)); + totalLiquidity = reserves[0] + reserves[1].divWadUp(params.price); internalParams[poolId].price = params.price; internalParams[poolId].swapFee = params.swapFee; From 6001c4eead1542e4feefe9047f23a51058ca591b Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 18:12:37 +0400 Subject: [PATCH 099/119] feat: remove L from ConstantSum init pool data --- src/ConstantSum/ConstantSumMath.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ConstantSum/ConstantSumMath.sol b/src/ConstantSum/ConstantSumMath.sol index a95ba4b2..b2bdba67 100644 --- a/src/ConstantSum/ConstantSumMath.sol +++ b/src/ConstantSum/ConstantSumMath.sol @@ -26,11 +26,10 @@ function computeInitialPoolData( ) pure returns (bytes memory) { // The pool can be initialized with any non-negative amount of rx, and ry. // so we have to allow a user to pass an amount of both even if one is zero. - uint256 L = rx.mulWadDown(params.price) + ry; uint256[] memory reserves = new uint256[](2); reserves[0] = rx; reserves[1] = ry; - return abi.encode(reserves, L, params); + return abi.encode(reserves, params); } function computeDeltaLiquidity( From 276ceb3f9f99dd7aec3af62c721d2d8291e14e9a Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 18:17:03 +0400 Subject: [PATCH 100/119] feat: reintroduce EPSILON --- src/lib/StrategyLib.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/StrategyLib.sol b/src/lib/StrategyLib.sol index d1d60387..1b5827db 100644 --- a/src/lib/StrategyLib.sol +++ b/src/lib/StrategyLib.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; +int256 constant EPSILON = 30; uint256 constant HALF = 0.5e18; uint256 constant ONE = 1e18; uint256 constant TWO = 2e18; From 3333eaf81e967666c95dc89bebe681af870a7268 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 18:17:19 +0400 Subject: [PATCH 101/119] feat: use EPSILON to check invariant in ConstantSum ini --- src/ConstantSum/ConstantSum.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index f8837503..a5702aef 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -13,6 +13,7 @@ import { decodeControllerUpdate } from "./ConstantSumUtils.sol"; import { PairStrategy, IStrategy, Pool } from "src/PairStrategy.sol"; +import { EPSILON } from "src/lib/StrategyLib.sol"; struct InternalParams { uint256 price; @@ -80,7 +81,7 @@ contract ConstantSum is PairStrategy { invariant = tradingFunction(reserves, totalLiquidity, abi.encode(params)); - valid = invariant >= 0; + valid = invariant >= 0 && invariant <= EPSILON; return (valid, invariant, reserves, totalLiquidity); } From e5db5acec7b081af8db29963f2e658918eafd215 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 18:17:58 +0400 Subject: [PATCH 102/119] feat: use EPSILON to check invariant in G3M init --- src/GeometricMean/GeometricMean.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GeometricMean/GeometricMean.sol b/src/GeometricMean/GeometricMean.sol index 98cc0af8..00db4ffd 100644 --- a/src/GeometricMean/GeometricMean.sol +++ b/src/GeometricMean/GeometricMean.sol @@ -11,7 +11,7 @@ import { computeDeltaGivenDeltaLRoundDown, computeSwapDeltaLiquidity } from "./G3MMath.sol"; -import { ONE } from "src/lib/StrategyLib.sol"; +import { ONE, EPSILON } from "src/lib/StrategyLib.sol"; /** * @dev Parameterization of the GeometricMean curve. @@ -105,7 +105,7 @@ contract GeometricMean is PairStrategy { abi.decode(getPoolParams(poolId), (GeometricMeanParams)) ); - state.valid = state.invariant >= 0; + state.valid = state.invariant >= 0 && state.invariant <= EPSILON; return (state.valid, state.invariant, state.reserves, state.totalLiquidity); From 5a7b2bc51beac7392b970ea628de35654b386c6e Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 18:18:38 +0400 Subject: [PATCH 103/119] feat: use EPSILON to check invariant in LogNormal init --- src/LogNormal/LogNormal.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/LogNormal/LogNormal.sol b/src/LogNormal/LogNormal.sol index fd7d6fea..ef014a8a 100644 --- a/src/LogNormal/LogNormal.sol +++ b/src/LogNormal/LogNormal.sol @@ -17,6 +17,7 @@ import { decodeWidthUpdate, decodeControllerUpdate } from "src/LogNormal/LogNormalUtils.sol"; +import { EPSILON } from "src/lib/StrategyLib.sol"; enum UpdateCode { Invalid, @@ -87,7 +88,7 @@ contract LogNormal is PairStrategy { invariant = tradingFunction(reserves, totalLiquidity, getPoolParams(poolId)); - valid = invariant >= 0; + valid = invariant >= 0 && invariant <= EPSILON; } /// @inheritdoc IStrategy From 6ecc8e7fd11a1e05a5243f4871155478b27d8d29 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 29 Mar 2024 18:19:20 +0400 Subject: [PATCH 104/119] feat: use EPSILON to check invariant in NTokenG3M init --- src/NTokenGeometricMean/NTokenGeometricMean.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NTokenGeometricMean/NTokenGeometricMean.sol b/src/NTokenGeometricMean/NTokenGeometricMean.sol index 14dffa28..fffa72ce 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMean.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMean.sol @@ -15,7 +15,7 @@ import { computeDeltaGivenDeltaLRoundDown, computeSwapDeltaLiquidity } from "src/NTokenGeometricMean/NTokenGeometricMeanMath.sol"; -import { ONE } from "src/lib/StrategyLib.sol"; +import { ONE, EPSILON } from "src/lib/StrategyLib.sol"; /** * @dev Parameterization of the GeometricMean curve. @@ -123,7 +123,7 @@ contract NTokenGeometricMean is NTokenStrategy { state.reserves, state.totalLiquidity, getPoolParams(poolId) ); - bool valid = invariant >= 0; + bool valid = invariant >= 0 && invariant <= EPSILON; return (valid, invariant, state.reserves, state.totalLiquidity); } From c6209b830967ce57d1233fc560c127b285e6710c Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Fri, 29 Mar 2024 16:18:37 -0400 Subject: [PATCH 105/119] fix: fix `SetsValue*` tests to account for instantaneous `lastComputedValue` change --- test/lib/DynamicParamLib.t.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/lib/DynamicParamLib.t.sol b/test/lib/DynamicParamLib.t.sol index dea32839..bf5dd94c 100644 --- a/test/lib/DynamicParamLib.t.sol +++ b/test/lib/DynamicParamLib.t.sol @@ -100,7 +100,8 @@ contract DynamicParamLibTest is Test { function test_DynamicParamLib_set_SetsValueIncrease() public { initStoredParam(10, 0, 0, 0); storedParam.set(20, 10); - assertEq(storedParam.lastComputedValue, 10); + // immediately updates `lastComputedValue` to 11 + assertEq(storedParam.lastComputedValue, 11); assertEq(storedParam.updateEnd, 10); assertEq(storedParam.lastUpdateAt, block.timestamp); assertEq(storedParam.updatePerSecond, 1); @@ -109,7 +110,8 @@ contract DynamicParamLibTest is Test { function test_DynamicParamLib_set_SetsValueDecrease() public { initStoredParam(20, 0, 0, 0); storedParam.set(10, 10); - assertEq(storedParam.lastComputedValue, 20); + // immediately updates `lastComputedValue` to 19 + assertEq(storedParam.lastComputedValue, 19); assertEq(storedParam.updateEnd, 10); assertEq(storedParam.lastUpdateAt, block.timestamp); assertEq(storedParam.updatePerSecond, -1); From 0f0ce531850981954ccfb01137bd12a06a9e2561 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 1 Apr 2024 11:35:11 +0400 Subject: [PATCH 106/119] fix: remove computeLGivenY in G3MMath --- src/GeometricMean/G3MMath.sol | 8 -------- src/GeometricMean/GeometricMeanSolver.sol | 1 - 2 files changed, 9 deletions(-) diff --git a/src/GeometricMean/G3MMath.sol b/src/GeometricMean/G3MMath.sol index 0e409569..c3cb4bc6 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -47,14 +47,6 @@ function computeLGivenX( return x.mulWadUp(uint256(b)); } -function computeLGivenY( - uint256 y, - uint256 S, - GeometricMeanParams memory params -) pure returns (uint256) { - return y.mulWadUp(params.wX).divWadUp(params.wY.mulWadUp(S)); -} - function computeXGivenL( uint256 L, uint256 S, diff --git a/src/GeometricMean/GeometricMeanSolver.sol b/src/GeometricMean/GeometricMeanSolver.sol index 6cf9d359..54cf40ae 100644 --- a/src/GeometricMean/GeometricMeanSolver.sol +++ b/src/GeometricMean/GeometricMeanSolver.sol @@ -17,7 +17,6 @@ import { computeLGivenX, computeY, computeX, - computeLGivenY, computeNextLiquidity, computeNextRx, computeNextRy, From 8a6788fdec30243c2e7e0196acf0a98448c78584 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 1 Apr 2024 11:39:25 +0400 Subject: [PATCH 107/119] fix: remove computeYGivenL --- src/GeometricMean/G3MMath.sol | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/GeometricMean/G3MMath.sol b/src/GeometricMean/G3MMath.sol index a3a7d7ae..2c01ab47 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -55,14 +55,6 @@ function computeXGivenL( return params.wX.mulWadUp(L).divWadUp(params.wY.mulWadUp(S)); } -function computeYGivenL( - uint256 L, - uint256 S, - GeometricMeanParams memory params -) pure returns (uint256) { - return params.wY.mulWadUp(L).divWadUp(params.wX.mulWadUp(S)); -} - function computeAllocationGivenDeltaX( uint256 deltaX, uint256 rX, From 8a7fa2807e9ee4b60ee08cf3711b00cea6ad7dac Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 1 Apr 2024 11:43:33 +0400 Subject: [PATCH 108/119] fix: use mulDivUp in computeLGivenX --- src/GeometricMean/G3MMath.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GeometricMean/G3MMath.sol b/src/GeometricMean/G3MMath.sol index 2c01ab47..c028449f 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -42,7 +42,7 @@ function computeLGivenX( uint256 S, GeometricMeanParams memory params ) pure returns (uint256) { - int256 a = int256(params.wY.divWadUp(params.wX).mulWadUp(S)); + int256 a = int256(params.wY.mulDivUp(S, params.wX)); int256 b = a.powWad(int256(params.wY)); return x.mulWadUp(uint256(b)); } From 03ea7558e1878a87e828d060abd4d341ffa91b07 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 1 Apr 2024 11:43:47 +0400 Subject: [PATCH 109/119] fix: delete unused imports in G3MSolver --- src/GeometricMean/GeometricMeanSolver.sol | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/GeometricMean/GeometricMeanSolver.sol b/src/GeometricMean/GeometricMeanSolver.sol index 6562798b..885f5fe1 100644 --- a/src/GeometricMean/GeometricMeanSolver.sol +++ b/src/GeometricMean/GeometricMeanSolver.sol @@ -12,19 +12,14 @@ import { } from "./G3MUtils.sol"; import { computeInitialPoolData, - computeL, - computePrice, - computeLGivenX, - computeY, - computeX, - computeNextLiquidity, computeNextRx, computeNextRy, computeTradingFunction, computeAllocationGivenDeltaX, computeAllocationGivenDeltaY, computeDeallocationGivenDeltaX, - computeDeallocationGivenDeltaY + computeDeallocationGivenDeltaY, + computePrice } from "./G3MMath.sol"; contract GeometricMeanSolver { From 11861e88db2bae3924ebde3b6728281a8639dc5a Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 1 Apr 2024 11:44:49 +0400 Subject: [PATCH 110/119] fix: remove computeXGivenL in G3MMath --- src/GeometricMean/G3MMath.sol | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/GeometricMean/G3MMath.sol b/src/GeometricMean/G3MMath.sol index c028449f..e814ebe5 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -47,14 +47,6 @@ function computeLGivenX( return x.mulWadUp(uint256(b)); } -function computeXGivenL( - uint256 L, - uint256 S, - GeometricMeanParams memory params -) pure returns (uint256) { - return params.wX.mulWadUp(L).divWadUp(params.wY.mulWadUp(S)); -} - function computeAllocationGivenDeltaX( uint256 deltaX, uint256 rX, From 1316bc2728ef6fd1683f5bad2bc9463234eeea4d Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 1 Apr 2024 11:45:40 +0400 Subject: [PATCH 111/119] fix: remove computeX in G3MMath --- src/GeometricMean/G3MMath.sol | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/GeometricMean/G3MMath.sol b/src/GeometricMean/G3MMath.sol index e814ebe5..76fea8bc 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -99,14 +99,6 @@ function computeY( return params.wY.divWadDown(params.wX).mulWadDown(S).mulWadDown(x); } -function computeX( - uint256 y, - uint256 S, - GeometricMeanParams memory params -) pure returns (uint256) { - return params.wX.divWadDown(params.wY.mulWadDown(S)).mulWadDown(y); -} - function computeL( uint256 x, uint256 y, From cda904a3ea02e30c63b704af2d8aac8eb9b69c9c Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 1 Apr 2024 17:58:15 +0400 Subject: [PATCH 112/119] fix: use computeDeltaLiquidity in ConstantSum init --- src/ConstantSum/ConstantSum.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index a5702aef..ef0ef9ee 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -67,7 +67,8 @@ contract ConstantSum is PairStrategy { ConstantSumParams memory params; (reserves, params) = abi.decode(data, (uint256[], ConstantSumParams)); - totalLiquidity = reserves[0] + reserves[1].divWadUp(params.price); + totalLiquidity = + computeDeltaLiquidity(reserves[0], reserves[1], params.price); if (pool.reserves.length != 2 || reserves.length != 2) { revert InvalidReservesLength(); From aa9e127eb9670296cdd24fa3cb803cd74a103d1a Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 1 Apr 2024 18:24:38 +0400 Subject: [PATCH 113/119] test: increase default X reserve for LogNormal pools --- test/LogNormal/unit/SetUp.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/LogNormal/unit/SetUp.sol b/test/LogNormal/unit/SetUp.sol index 2866eb4e..733fcabd 100644 --- a/test/LogNormal/unit/SetUp.sol +++ b/test/LogNormal/unit/SetUp.sol @@ -27,7 +27,7 @@ contract LogNormalSetUp is SetUp { controller: address(this) }); - uint256 defaultReserveX = ONE; + uint256 defaultReserveX = 100 ether; uint256 defaultReserveXDeep = ONE * 10_000_000; uint256 defaultPrice = ONE; From f597df3f42c27088e434cb92022ee59ae713345e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Lakhal?= <39790678+clemlak@users.noreply.github.com> Date: Tue, 2 Apr 2024 01:24:23 +0400 Subject: [PATCH 114/119] Fix/unused imports ntokeng3m utils (#109) * fix: use muldiv in NTokenG3M math * fix: remove unnused imports in NTokenG3MUtils --- src/NTokenGeometricMean/NTokenGeometricMeanUtils.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/NTokenGeometricMean/NTokenGeometricMeanUtils.sol b/src/NTokenGeometricMean/NTokenGeometricMeanUtils.sol index bf40e314..7e655bf1 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMeanUtils.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMeanUtils.sol @@ -7,8 +7,6 @@ import { } from "src/NTokenGeometricMean/NTokenGeometricMean.sol"; import { computeReserveFromNumeraire, - computeL, - computeTradingFunction, computeNextLiquidity } from "./NTokenGeometricMeanMath.sol"; From b0c9f6fc87cfd731e07e9778412d874ece6ebb78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Lakhal?= <39790678+clemlak@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:49:09 +0400 Subject: [PATCH 115/119] Fix/ntoken accumulator (#105) * fix: accumulator var in NTokenG3M not being updated * fix: accumulator in NTokenG3MMath, use mulDivDown * add missing accumulator assignments, fix NToken init tests * wip swap fixes * fix: deltaLiquidity calculation in ntoken pool * fix: computeAllocationDeltas should round down for deltaL * fix: rounding on `allo/deallo` deltas given deltaT * initialize accumulator to ONE on getNextLiquidity * rm console2 --------- Co-authored-by: kinrezc --- src/GeometricMean/GeometricMeanSolver.sol | 14 +++++-- .../NTokenGeometricMeanMath.sol | 41 ++++++++++--------- .../NTokenGeometricMeanSolver.sol | 14 +++++-- .../NTokenGeometricMean.t.sol | 39 ++++++++++++++---- 4 files changed, 73 insertions(+), 35 deletions(-) diff --git a/src/GeometricMean/GeometricMeanSolver.sol b/src/GeometricMean/GeometricMeanSolver.sol index 885f5fe1..fc87794d 100644 --- a/src/GeometricMean/GeometricMeanSolver.sol +++ b/src/GeometricMean/GeometricMeanSolver.sol @@ -19,7 +19,8 @@ import { computeAllocationGivenDeltaY, computeDeallocationGivenDeltaX, computeDeallocationGivenDeltaY, - computePrice + computePrice, + computeSwapDeltaLiquidity } from "./G3MMath.sol"; contract GeometricMeanSolver { @@ -186,9 +187,14 @@ contract GeometricMeanSolver { state.outWeight = params.wX; } - state.fees = amountIn.mulWadUp(params.swapFee); - state.deltaLiquidity = pool.totalLiquidity.divWadUp(state.inReserve) - .mulWadUp(state.fees).mulWadUp(state.inWeight); + state.deltaLiquidity = computeSwapDeltaLiquidity( + amountIn, + state.inReserve, + pool.totalLiquidity, + state.inWeight, + params.swapFee + ); + { uint256 n = (pool.totalLiquidity + state.deltaLiquidity); uint256 d = uint256( diff --git a/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol b/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol index d46ecdbf..cb5c3f70 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol @@ -19,7 +19,7 @@ function computeTradingFunction( uint256 a = uint256( int256(reserves[i].divWadDown(L)).powWad(int256(params.weights[i])) ); - accumulator.mulWadUp(a); + accumulator = accumulator.mulWadUp(a); } return int256(accumulator) - int256(ONE); @@ -45,28 +45,35 @@ function computeL( uint256[] memory reserves, NTokenGeometricMeanParams memory params ) pure returns (uint256) { - uint256 accumulator; - + uint256 accumulator = ONE; for (uint256 i = 0; i < reserves.length; i++) { uint256 a = uint256(int256(reserves[i]).powWad(int256(params.weights[i]))); - if (accumulator != 0) { - accumulator += a; - } else { - accumulator.mulWadUp(a); - } + accumulator = accumulator.mulWadUp(a); } return accumulator; } +function computePrice( + uint256 indexT, + uint256[] memory reserves, + NTokenGeometricMeanParams memory params +) pure returns (uint256 price) { + uint256 n = reserves[indexT].divWadDown(params.weights[indexT]); + uint256 d = reserves[reserves.length - 1].divWadDown( + params.weights[reserves.length - 1] + ); + price = n.divWadUp(d); +} + function computeReserveFromNumeraire( uint256 amountNumeraire, uint256 S, uint256 wT, uint256 wNumeraire ) pure returns (uint256) { - return wT.divWadDown(wNumeraire.mulWadDown(S)).mulWadDown(amountNumeraire); + return wT.mulDivDown(amountNumeraire, wNumeraire.mulWadUp(S)); } function computeAllocationDeltasGivenDeltaT( @@ -84,7 +91,7 @@ function computeAllocationDeltasGivenDeltaT( } } - uint256 deltaL = a.mulWadUp(totalLiquidity); + uint256 deltaL = a.mulWadDown(totalLiquidity); return (reserveDeltas, deltaL); } @@ -104,7 +111,7 @@ function computeDeallocationDeltasGivenDeltaT( } } - uint256 deltaL = a.mulWadDown(totalLiquidity); + uint256 deltaL = a.mulWadUp(totalLiquidity); return (reserveDeltas, deltaL); } @@ -114,15 +121,11 @@ function computeNextLiquidity( uint256[] memory reserves, NTokenGeometricMeanParams memory params ) pure returns (uint256 L) { - uint256 accumulator; + uint256 accumulator = ONE; for (uint256 i = 0; i < reserves.length; i++) { uint256 a = uint256(int256(reserves[i]).powWad(int256(params.weights[i]))); - if (accumulator != 0) { - accumulator.mulWadUp(a); - } else { - accumulator = a; - } + accumulator = accumulator.mulWadUp(a); } return accumulator; } @@ -134,7 +137,7 @@ function computeSwapDeltaLiquidity( uint256 weight, uint256 swapFee ) pure returns (uint256) { - return weight.mulWadUp(ONE - swapFee).mulWadUp(totalLiquidity).mulWadUp( - amountIn.divWadUp(reserve) + return weight.mulWadDown(swapFee).mulWadDown(totalLiquidity).mulWadDown( + amountIn.divWadDown(reserve) ); } diff --git a/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol b/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol index ba4de9f7..c9f37212 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol @@ -14,7 +14,8 @@ import { import { computeAllocationDeltasGivenDeltaT, computeDeallocationDeltasGivenDeltaT, - computeNextLiquidity + computeNextLiquidity, + computeSwapDeltaLiquidity } from "src/NTokenGeometricMean/NTokenGeometricMeanMath.sol"; import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; @@ -81,9 +82,14 @@ contract NTokenGeometricMeanSolver { state.inWeight = params.weights[tokenInIndex]; state.outWeight = params.weights[tokenOutIndex]; - state.fees = amountIn.mulWadUp(params.swapFee); - state.deltaLiquidity = pool.totalLiquidity.divWadUp(state.inReserve) - .mulWadUp(state.fees).mulWadUp(state.inWeight); + state.deltaLiquidity = computeSwapDeltaLiquidity( + amountIn, + state.inReserve, + pool.totalLiquidity, + state.inWeight, + params.swapFee + ); + { uint256 n = (pool.totalLiquidity + state.deltaLiquidity); uint256 accumulator = FixedPointMathLib.WAD; diff --git a/test/NTokenGeometricMean/NTokenGeometricMean.t.sol b/test/NTokenGeometricMean/NTokenGeometricMean.t.sol index 7d18dc1e..98a33549 100644 --- a/test/NTokenGeometricMean/NTokenGeometricMean.t.sol +++ b/test/NTokenGeometricMean/NTokenGeometricMean.t.sol @@ -10,6 +10,9 @@ import "src/NTokenGeometricMean/NTokenGeometricMeanSolver.sol"; import "src/interfaces/IDFMM.sol"; import "src/DFMM.sol"; +import { computePrice } from + "src/NTokenGeometricMean/NTokenGeometricMeanMath.sol"; + contract NTokenGeometricMeanTest is Test { using stdStorage for StdStorage; using FixedPointMathLib for uint256; @@ -69,7 +72,7 @@ contract NTokenGeometricMeanTest is Test { NTokenGeometricMeanParams memory params = NTokenGeometricMeanParams({ weights: weights, - swapFee: 0, + swapFee: TEST_SWAP_FEE, controller: address(this) }); @@ -91,7 +94,7 @@ contract NTokenGeometricMeanTest is Test { /// @dev Initializes a basic pool in dfmm. modifier basic() { vm.warp(0); - uint256 reserveNumeraire = 1 ether; + uint256 reserveNumeraire = 10 ether; uint256 price = ONE; uint256 w = 0.25 ether; uint256[] memory weights = new uint256[](4); @@ -106,7 +109,7 @@ contract NTokenGeometricMeanTest is Test { NTokenGeometricMeanParams memory params = NTokenGeometricMeanParams({ weights: weights, - swapFee: 0, + swapFee: TEST_SWAP_FEE, controller: address(this) }); @@ -129,7 +132,7 @@ contract NTokenGeometricMeanTest is Test { /// @dev Initializes a basic pool in dfmm. modifier basic_70_10_10_10() { vm.warp(0); - uint256 reserveNumeraire = 1 ether; + uint256 reserveNumeraire = 2 ether; uint256 price = ONE; uint256 w = 0.25 ether; uint256[] memory weights = new uint256[](4); @@ -218,7 +221,7 @@ contract NTokenGeometricMeanTest is Test { function test_4_token_deallocate_given_delta_t() public basic { (uint256[] memory dReserves, uint256 dLiquidity) = - solver.getAllocationDeltasGivenDeltaT(POOL_ID, 1, 0.5 ether); + solver.getDeallocationDeltasGivenDeltaT(POOL_ID, 1, 0.5 ether); bytes memory data = abi.encode(dReserves, dLiquidity); @@ -245,8 +248,8 @@ contract NTokenGeometricMeanTest is Test { console2.log(postL); } - function test_4_token_simulate_swap() public basic { - uint256 amountIn = 0.1 ether; + function test_4_token_simulate_swap_uniform() public basic { + uint256 amountIn = 1 ether; uint256 tokenInIndex = 0; uint256 tokenOutIndex = 1; @@ -254,7 +257,27 @@ contract NTokenGeometricMeanTest is Test { solver.simulateSwap(POOL_ID, tokenInIndex, tokenOutIndex, amountIn); console2.log("amountOut", amountOut); console2.log("valid", valid); + + (uint256[] memory preReserves,) = + solver.getReservesAndLiquidity(POOL_ID); + NTokenGeometricMeanParams memory params = solver.getPoolParams(POOL_ID); + + console2.log( + "price tOut", computePrice(tokenOutIndex, preReserves, params) + ); + console2.log( + "price tIn", computePrice(tokenInIndex, preReserves, params) + ); + dfmm.swap(POOL_ID, address(this), data); + (uint256[] memory postReserves,) = + solver.getReservesAndLiquidity(POOL_ID); + console2.log( + "price tOut", computePrice(tokenOutIndex, postReserves, params) + ); + console2.log( + "price tIn", computePrice(tokenInIndex, postReserves, params) + ); } function test_4_token_compute_price() public basic { @@ -312,7 +335,7 @@ contract NTokenGeometricMeanTest is Test { basic_70_10_10_10 { (uint256[] memory dReserves, uint256 dLiquidity) = - solver.getAllocationDeltasGivenDeltaT(POOL_ID, 1, 0.2 ether); + solver.getDeallocationDeltasGivenDeltaT(POOL_ID, 1, 0.2 ether); bytes memory data = abi.encode(dReserves, dLiquidity); From 1b7a4dd6d051d3a0c1d35b3a8ca3efa586879c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Lakhal?= <39790678+clemlak@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:49:22 +0400 Subject: [PATCH 116/119] fix: optimize token decimals check in DFMM init (#112) --- src/DFMM.sol | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index aea038c7..c20e259b 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -110,6 +110,11 @@ contract DFMM is IDFMM { for (uint256 i = 0; i < tokensLength; i++) { address token = params.tokens[i]; + uint256 decimals = ERC20(params.tokens[i]).decimals(); + + if (decimals > 18 || decimals < 6) { + revert InvalidTokenDecimals(); + } for (uint256 j = i + 1; j < tokensLength; j++) { if (token == params.tokens[j]) { @@ -118,14 +123,6 @@ contract DFMM is IDFMM { } } - for (uint256 i = 0; i < tokensLength; i++) { - uint256 decimals = ERC20(params.tokens[i]).decimals(); - - if (decimals > 18 || decimals < 6) { - revert InvalidTokenDecimals(); - } - } - _transferFrom(params.tokens, reserves); emit Init( From 78686871f361a9a3cc91584af93d8ec391e87d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Lakhal?= <39790678+clemlak@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:50:13 +0400 Subject: [PATCH 117/119] fix: computeNextLiquidity in G3MMath (#114) --- src/GeometricMean/G3MMath.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GeometricMean/G3MMath.sol b/src/GeometricMean/G3MMath.sol index 76fea8bc..cdbd8236 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -199,7 +199,7 @@ function computeNextLiquidity( findRootLiquidity ); - if (rootInput == 0) { + if (computeTradingFunction(rX, rY, rootInput, params) == 0) { L = rootInput; } else { L = lowerInput; From af5c660cd338c2428774b1d1c46d5f81f55c0e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Lakhal?= <39790678+clemlak@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:50:30 +0400 Subject: [PATCH 118/119] fix: NatSpec in computeLGivenX in LogNormalMath (#113) * fix: NatSpec in computeLGivenX in LogNormalMath * Update src/LogNormal/LogNormalMath.sol --------- Co-authored-by: Matt Czernik --- src/LogNormal/LogNormalMath.sol | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/LogNormal/LogNormalMath.sol b/src/LogNormal/LogNormalMath.sol index 801e6c33..303c371b 100644 --- a/src/LogNormal/LogNormalMath.sol +++ b/src/LogNormal/LogNormalMath.sol @@ -59,11 +59,16 @@ function computeHalfSigmaSquared(uint256 sigma) pure returns (uint256) { return HALF.mulWadDown(sigma.mulWadUp(sigma)); } -/// @dev Computes reserves L given rx, S. -/// @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 reserve L computed as L(x, s) = K * L_x(x, S) * Gaussian.cdf[d2(S, K, sigma, tau)] +/** + * @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, From 89e1a9653f6f4659daa14c7c514eb8da5f76b570 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Apr 2024 17:32:00 +0400 Subject: [PATCH 119/119] fix: rounding direction in computeTradingFunction in LogNormalMath --- src/LogNormal/LogNormalMath.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LogNormal/LogNormalMath.sol b/src/LogNormal/LogNormalMath.sol index 303c371b..63c544c3 100644 --- a/src/LogNormal/LogNormalMath.sol +++ b/src/LogNormal/LogNormalMath.sol @@ -22,7 +22,7 @@ function computeTradingFunction( LogNormalParams memory params ) pure returns (int256) { int256 a = Gaussian.ppf(int256(rX.divWadDown(L))); - int256 b = Gaussian.ppf(int256(rY.divWadDown(L.mulWadDown(params.mean)))); + int256 b = Gaussian.ppf(int256(rY.divWadDown(L.mulWadUp(params.mean)))); return a + b + int256(params.width); }