From e1d1a4dd0a73dfbb21512b820484664aac8ec991 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 5 Apr 2024 14:30:16 +0400 Subject: [PATCH 01/16] fix: use token variable instead of array value in DFMM init loop --- src/DFMM.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index c20e259b..29f18b5d 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -110,7 +110,7 @@ contract DFMM is IDFMM { for (uint256 i = 0; i < tokensLength; i++) { address token = params.tokens[i]; - uint256 decimals = ERC20(params.tokens[i]).decimals(); + uint256 decimals = ERC20(token).decimals(); if (decimals > 18 || decimals < 6) { revert InvalidTokenDecimals(); From 2c700a62315e5fd373b13757e69dd7c255c2ed7d Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 5 Apr 2024 14:31:11 +0400 Subject: [PATCH 02/16] fix: replace MIN_WIDTH by MIN_MEAN in LogNormal --- src/LogNormal/LogNormal.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LogNormal/LogNormal.sol b/src/LogNormal/LogNormal.sol index d3fb8587..d749b62d 100644 --- a/src/LogNormal/LogNormal.sol +++ b/src/LogNormal/LogNormal.sol @@ -88,7 +88,7 @@ contract LogNormal is PairStrategy { (reserves, totalLiquidity, params) = abi.decode(data, (uint256[], uint256, LogNormalParams)); - if (params.mean < MIN_WIDTH || params.mean > MAX_MEAN) { + if (params.mean < MIN_MEAN || params.mean > MAX_MEAN) { revert InvalidMean(); } From 13aa5097f07174dbccd1c30509b33ba2058d52d8 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 5 Apr 2024 14:36:46 +0400 Subject: [PATCH 03/16] fix: maxDeltaL check in ConstantSum --- src/ConstantSum/ConstantSum.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index ef0ef9ee..c4bba231 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -147,7 +147,7 @@ contract ConstantSum is PairStrategy { deltaLiquidity = computeDeltaLiquidity(deltaX, deltaY, internalParams[poolId].price); - if (maxDeltaL > deltaLiquidity) revert InvalidDeltaLiquidity(); + if (deltaLiquidity > maxDeltaL) revert InvalidDeltaLiquidity(); deltas = new uint256[](2); deltas[0] = deltaX; From 016b81a75b3fc8c300a91d004690b2abe38f8ae2 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 5 Apr 2024 15:17:15 +0400 Subject: [PATCH 04/16] feat: remove computeSwapDeltaLiquidity from ConstantSumSolver simulateSwap --- src/ConstantSum/ConstantSumSolver.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ConstantSum/ConstantSumSolver.sol b/src/ConstantSum/ConstantSumSolver.sol index 644a2e77..2dea2a97 100644 --- a/src/ConstantSum/ConstantSumSolver.sol +++ b/src/ConstantSum/ConstantSumSolver.sol @@ -59,7 +59,6 @@ contract ConstantSumSolver { SimulateSwapState memory state; if (swapXIn) { - computeSwapDeltaLiquidity(amountIn, poolParams, true); state.amountOut = amountIn.mulWadDown(poolParams.price).mulWadDown( ONE - poolParams.swapFee ); @@ -68,7 +67,6 @@ contract ConstantSumSolver { revert NotEnoughLiquidity(); } } else { - computeSwapDeltaLiquidity(amountIn, poolParams, false); state.amountOut = (ONE - poolParams.swapFee).mulWadDown(amountIn) .divWadDown(poolParams.price); From 348dbedcc834f806d8fa188f1e79754168502ae5 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 5 Apr 2024 15:20:23 +0400 Subject: [PATCH 05/16] feat: remove SimulateSwapState in ConstantSumSolver simulateSwap --- src/ConstantSum/ConstantSumSolver.sol | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/ConstantSum/ConstantSumSolver.sol b/src/ConstantSum/ConstantSumSolver.sol index 2dea2a97..c6b7f252 100644 --- a/src/ConstantSum/ConstantSumSolver.sol +++ b/src/ConstantSum/ConstantSumSolver.sol @@ -41,11 +41,6 @@ contract ConstantSumSolver { return computeInitialPoolData(rx, ry, params); } - struct SimulateSwapState { - uint256 amountOut; - uint256 deltaLiquidity; - } - function simulateSwap( uint256 poolId, bool swapXIn, @@ -56,21 +51,21 @@ contract ConstantSumSolver { IStrategy(strategy).getPoolParams(poolId), (ConstantSumParams) ); - SimulateSwapState memory state; + uint256 amountOut; if (swapXIn) { - state.amountOut = amountIn.mulWadDown(poolParams.price).mulWadDown( + amountOut = amountIn.mulWadDown(poolParams.price).mulWadDown( ONE - poolParams.swapFee ); - if (pool.reserves[1] < state.amountOut) { + if (pool.reserves[1] < amountOut) { revert NotEnoughLiquidity(); } } else { - state.amountOut = (ONE - poolParams.swapFee).mulWadDown(amountIn) + amountOut = (ONE - poolParams.swapFee).mulWadDown(amountIn) .divWadDown(poolParams.price); - if (pool.reserves[0] < state.amountOut) { + if (pool.reserves[0] < amountOut) { revert NotEnoughLiquidity(); } } @@ -78,15 +73,15 @@ contract ConstantSumSolver { bytes memory swapData; if (swapXIn) { - swapData = abi.encode(0, 1, amountIn, state.amountOut); + swapData = abi.encode(0, 1, amountIn, amountOut); } else { - swapData = abi.encode(1, 0, amountIn, state.amountOut); + swapData = abi.encode(1, 0, amountIn, amountOut); } (bool valid,,,,,,) = IStrategy(strategy).validateSwap( address(this), poolId, pool, swapData ); - return (valid, state.amountOut, swapData); + return (valid, amountOut, swapData); } function preparePriceUpdate(uint256 newPrice) From 50d121a08f0f4223217d64a3a7540f68b22faeef Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 5 Apr 2024 16:45:42 +0400 Subject: [PATCH 06/16] fix: wrap for loop with unchecked to save some gas --- src/DFMM.sol | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index 29f18b5d..2bb24dd0 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -116,9 +116,11 @@ contract DFMM is IDFMM { revert InvalidTokenDecimals(); } - for (uint256 j = i + 1; j < tokensLength; j++) { - if (token == params.tokens[j]) { - revert InvalidDuplicateTokens(); + unchecked { + for (uint256 j = i + 1; j < tokensLength; j++) { + if (token == params.tokens[j]) { + revert InvalidDuplicateTokens(); + } } } } From c086b1b981cda28f4268d0cabff4a9d65e10bbd4 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 5 Apr 2024 16:59:41 +0400 Subject: [PATCH 07/16] fix: skip transfer if amount is 0 --- src/DFMM.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DFMM.sol b/src/DFMM.sol index 2bb24dd0..7d5db91b 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -336,6 +336,7 @@ contract DFMM is IDFMM { } else { uint256 downscaledAmount = downscaleDown(amount, computeScalingFactor(token)); + if (downscaledAmount == 0) return; uint256 preBalance = ERC20(token).balanceOf(address(this)); SafeTransferLib.safeTransfer(ERC20(token), to, downscaledAmount); uint256 postBalance = ERC20(token).balanceOf(address(this)); From 38917c194aa036701a3a1c34ea2d15c06c2df0a4 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 5 Apr 2024 11:41:39 -0700 Subject: [PATCH 08/16] fix(DOS): cross pool dos vector removed by refunding eth only if weth token --- src/DFMM.sol | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index c20e259b..19432dea 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -313,10 +313,13 @@ contract DFMM is IDFMM { if (postBalance < preBalance + downscaledAmount) { revert InvalidTransfer(); } - } - if (address(this).balance > 0) { - SafeTransferLib.safeTransferETH(msg.sender, address(this).balance); + // Refund any excess native ether if the token is WETH. + if (token == weth && address(this).balance > 0) { + SafeTransferLib.safeTransferETH( + msg.sender, address(this).balance + ); + } } } From 624a29aecd12dc0d6673fbe60336f7069c9d0130 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 5 Apr 2024 12:26:58 -0700 Subject: [PATCH 09/16] fix(native-eth): use msg.value to determine branch case, refund excess amounts --- src/DFMM.sol | 30 +++++++++++++++++++----------- test/DFMM/unit/Init.t.sol | 3 +++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/DFMM.sol b/src/DFMM.sol index 19432dea..d2f1fa98 100644 --- a/src/DFMM.sol +++ b/src/DFMM.sol @@ -281,9 +281,13 @@ contract DFMM is IDFMM { // Internals /** - * @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. + * @notice Transfers `amounts` of `tokens` from the sender to the contract. + * @dev Note that for pools with `WETH` as a token, the contract will accept + * full payments in native ether. If the contract receives more ether than + * the amount of `WETH` needed, the contract will refund the remaining ether. + * If the contract receives less ether than the amount of `WETH` needed, the + * contract will refund the native ether and request the full amount of `WETH` + * needed instead. * @param tokens An array of token addresses to transfer. * @param amounts An array of amounts to transfer expressed in WAD. */ @@ -301,7 +305,9 @@ contract DFMM is IDFMM { downscaleUp(amount, computeScalingFactor(token)); uint256 preBalance = ERC20(token).balanceOf(address(this)); - if (token == weth && address(this).balance >= amount) { + // note: `msg.value` can be used safely in a loop because `weth` is a unique token, + // therefore we only enter this branch once. + if (token == weth && msg.value >= amount) { WETH(payable(weth)).deposit{ value: amount }(); } else { SafeTransferLib.safeTransferFrom( @@ -309,17 +315,19 @@ contract DFMM is IDFMM { ); } - uint256 postBalance = ERC20(token).balanceOf(address(this)); - if (postBalance < preBalance + downscaledAmount) { - revert InvalidTransfer(); - } - - // Refund any excess native ether if the token is WETH. - if (token == weth && address(this).balance > 0) { + // If not enough native ether was sent as payment + // or too much ether was sent, + // refund all the remaining ether back to the sender. + if (token == weth && msg.value != 0) { SafeTransferLib.safeTransferETH( msg.sender, address(this).balance ); } + + uint256 postBalance = ERC20(token).balanceOf(address(this)); + if (postBalance < preBalance + downscaledAmount) { + revert InvalidTransfer(); + } } } diff --git a/test/DFMM/unit/Init.t.sol b/test/DFMM/unit/Init.t.sol index 695d967d..a952e545 100644 --- a/test/DFMM/unit/Init.t.sol +++ b/test/DFMM/unit/Init.t.sol @@ -17,6 +17,9 @@ contract DFMMInit is DFMMSetUp, Script { bytes defaultData = abi.encode(valid, initialInvariant, defaultReserves, initialLiquidity); + /// @notice for handling ether refunds + receive() external payable { } + function test_DFMM_init_StoresStrategyInitialReservesAndLiquidity() public { From 1009b0f257d0762ba4f13627c36534018ed71a65 Mon Sep 17 00:00:00 2001 From: clemlak Date: Sun, 7 Apr 2024 14:04:25 +0400 Subject: [PATCH 10/16] fix: use ONE constant instead of actual value in G3M trading function --- 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 cdbd8236..40347d8a 100644 --- a/src/GeometricMean/G3MMath.sol +++ b/src/GeometricMean/G3MMath.sol @@ -18,7 +18,7 @@ function computeTradingFunction( 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); + return int256(a.mulWadUp(b)) - int256(ONE); } function computeDeltaGivenDeltaLRoundUp( From cc42a4ec828e963132b8172afc67b8a1c4f7f203 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 9 Apr 2024 14:10:36 +0300 Subject: [PATCH 11/16] fix: change rounding direction in NTokenG3M trading function --- src/NTokenGeometricMean/NTokenGeometricMeanMath.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol b/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol index 80c1db66..54dae967 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol @@ -17,7 +17,7 @@ function computeTradingFunction( uint256 accumulator = ONE; for (uint256 i = 0; i < reserves.length; i++) { uint256 a = uint256( - int256(reserves[i].divWadDown(L)).powWad(int256(params.weights[i])) + int256(reserves[i].divWadUp(L)).powWad(int256(params.weights[i])) ); accumulator = accumulator.mulWadUp(a); } From 039093f96d6eb532b414c0c1c380dbeb4690460d Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 9 Apr 2024 14:13:12 +0300 Subject: [PATCH 12/16] fix: change rounding direction in NTokenG3M computeSwapDeltaLiquidity --- src/NTokenGeometricMean/NTokenGeometricMeanMath.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol b/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol index 54dae967..6b26b85e 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol @@ -137,7 +137,7 @@ function computeSwapDeltaLiquidity( uint256 weight, uint256 swapFee ) pure returns (uint256) { - return weight.mulWadDown(swapFee).mulWadDown(totalLiquidity).mulWadDown( - amountIn.divWadDown(reserve) + return weight.mulWadUp(swapFee).mulWadUp(totalLiquidity).mulWadUp( + amountIn.divWadUp(reserve) ); } From 754916c7959ac6e80139eae25b82041957b42f7c Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 9 Apr 2024 14:15:26 +0300 Subject: [PATCH 13/16] fix: use mulDiv in NTokenG3M computeDeltaGivenDeltaLR --- src/NTokenGeometricMean/NTokenGeometricMeanMath.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol b/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol index 6b26b85e..7b7fcdd3 100644 --- a/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol +++ b/src/NTokenGeometricMean/NTokenGeometricMeanMath.sol @@ -30,7 +30,7 @@ function computeDeltaGivenDeltaLRoundUp( uint256 deltaLiquidity, uint256 totalLiquidity ) pure returns (uint256) { - return reserve.mulWadUp(deltaLiquidity.divWadUp(totalLiquidity)); + return reserve.mulDivUp(deltaLiquidity, totalLiquidity); } function computeDeltaGivenDeltaLRoundDown( @@ -38,7 +38,7 @@ function computeDeltaGivenDeltaLRoundDown( uint256 deltaLiquidity, uint256 totalLiquidity ) pure returns (uint256) { - return reserve.mulWadDown(deltaLiquidity.divWadDown(totalLiquidity)); + return reserve.mulDivDown(deltaLiquidity, totalLiquidity); } function computeL( From 6b5a3f524c139053a812f3e08ebf0623f346598e Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 9 Apr 2024 17:41:11 +0300 Subject: [PATCH 14/16] 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 0f26c7e0..537e0090 100644 --- a/src/ConstantSum/ConstantSumMath.sol +++ b/src/ConstantSum/ConstantSumMath.sol @@ -45,8 +45,8 @@ function computeSwapDeltaLiquidity( bool isSwapXForY ) pure returns (uint256) { if (isSwapXForY) { - return (params.swapFee).mulWadUp(delta); + return params.swapFee.mulWadUp(delta.mulWadUp(params.price)); } else { - return (params.swapFee).mulDivUp(delta, params.price); + return params.swapFee.mulWadUp(delta); } } From 54d10bb24b4f8508d775f947e587129cc8395e20 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 9 Apr 2024 17:48:48 +0300 Subject: [PATCH 15/16] fix: add rounding direction for ConstantSum computeDeltaLiquidity --- src/ConstantSum/ConstantSumMath.sol | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ConstantSum/ConstantSumMath.sol b/src/ConstantSum/ConstantSumMath.sol index 537e0090..7da95abb 100644 --- a/src/ConstantSum/ConstantSumMath.sol +++ b/src/ConstantSum/ConstantSumMath.sol @@ -31,7 +31,7 @@ function computeInitialPoolData( return abi.encode(reserves, params); } -function computeDeltaLiquidity( +function computeDeltaLiquidityRoundUp( uint256 deltaX, uint256 deltaY, uint256 price @@ -39,6 +39,14 @@ function computeDeltaLiquidity( return price.mulWadUp(deltaX) + deltaY; } +function computeDeltaLiquidityRoundDown( + uint256 deltaX, + uint256 deltaY, + uint256 price +) pure returns (uint256) { + return price.mulWadDown(deltaX) + deltaY; +} + function computeSwapDeltaLiquidity( uint256 delta, ConstantSumParams memory params, From 1e51776c4a66d2d627adfac587cf25cf7972a5bd Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 9 Apr 2024 17:51:40 +0300 Subject: [PATCH 16/16] fix: compute delta liquidity in ConstantSum with different rounding directions --- src/ConstantSum/ConstantSum.sol | 18 +++++++++++------- test/ConstantSum/unit/Allocate.t.sol | 5 +++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/ConstantSum/ConstantSum.sol b/src/ConstantSum/ConstantSum.sol index c4bba231..e3a5f0d3 100644 --- a/src/ConstantSum/ConstantSum.sol +++ b/src/ConstantSum/ConstantSum.sol @@ -5,7 +5,8 @@ import { FixedPointMathLib, computeTradingFunction, computeSwapDeltaLiquidity, - computeDeltaLiquidity + computeDeltaLiquidityRoundDown, + computeDeltaLiquidityRoundUp } from "./ConstantSumMath.sol"; import { decodePriceUpdate, @@ -67,8 +68,9 @@ contract ConstantSum is PairStrategy { ConstantSumParams memory params; (reserves, params) = abi.decode(data, (uint256[], ConstantSumParams)); - totalLiquidity = - computeDeltaLiquidity(reserves[0], reserves[1], params.price); + totalLiquidity = computeDeltaLiquidityRoundDown( + reserves[0], reserves[1], params.price + ); if (pool.reserves.length != 2 || reserves.length != 2) { revert InvalidReservesLength(); @@ -106,8 +108,9 @@ contract ConstantSum is PairStrategy { (uint256 deltaX, uint256 deltaY, uint256 minDeltaL) = abi.decode(data, (uint256, uint256, uint256)); - deltaLiquidity = - computeDeltaLiquidity(deltaX, deltaY, internalParams[poolId].price); + deltaLiquidity = computeDeltaLiquidityRoundDown( + deltaX, deltaY, internalParams[poolId].price + ); if (deltaLiquidity < minDeltaL) revert InvalidDeltaLiquidity(); deltas = new uint256[](2); @@ -145,8 +148,9 @@ contract ConstantSum is PairStrategy { (uint256 deltaX, uint256 deltaY, uint256 maxDeltaL) = abi.decode(data, (uint256, uint256, uint256)); - deltaLiquidity = - computeDeltaLiquidity(deltaX, deltaY, internalParams[poolId].price); + deltaLiquidity = computeDeltaLiquidityRoundUp( + deltaX, deltaY, internalParams[poolId].price + ); if (deltaLiquidity > maxDeltaL) revert InvalidDeltaLiquidity(); deltas = new uint256[](2); diff --git a/test/ConstantSum/unit/Allocate.t.sol b/test/ConstantSum/unit/Allocate.t.sol index 3e21e32b..b677ae7d 100644 --- a/test/ConstantSum/unit/Allocate.t.sol +++ b/test/ConstantSum/unit/Allocate.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import { ConstantSumSetUp } from "./SetUp.sol"; import { - computeDeltaLiquidity, + computeDeltaLiquidityRoundDown, ConstantSumParams } from "src/ConstantSum/ConstantSumMath.sol"; @@ -15,7 +15,8 @@ contract ConstantSumAllocateTest is ConstantSumSetUp { ConstantSumParams memory params = abi.decode(constantSum.getPoolParams(POOL_ID), (ConstantSumParams)); - uint256 deltaL = computeDeltaLiquidity(deltaX, deltaY, params.price); + uint256 deltaL = + computeDeltaLiquidityRoundDown(deltaX, deltaY, params.price); dfmm.allocate(POOL_ID, abi.encode(deltaX, deltaY, deltaL)); } }