diff --git a/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap index 5cd8344c..19f8a514 100644 --- a/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -199172 \ No newline at end of file +199182 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap index 26009a86..2f913ad6 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -231968 \ No newline at end of file +231988 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap index 9540249d..c6e7704a 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -245818 \ No newline at end of file +245843 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index 432d427d..9576e68b 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -303592 \ No newline at end of file +303622 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap index e97b81f9..8a2c58e1 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -225494 \ No newline at end of file +225514 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap index 9cee8b71..92a988c2 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -165555 \ No newline at end of file +165565 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap index 7ae7e611..33f1a96d 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -151117 \ No newline at end of file +151127 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap index 188a54c0..2d1d814a 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -174866 \ No newline at end of file +174876 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap b/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap index e5308bcc..28473c4c 100644 --- a/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap +++ b/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap @@ -1 +1 @@ -44191 \ No newline at end of file +44201 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap b/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap index 78251840..9ee997dc 100644 --- a/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap +++ b/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap @@ -1 +1 @@ -169490 \ No newline at end of file +169500 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap b/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap index c5c65d5c..230c9851 100644 --- a/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap +++ b/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap @@ -1 +1 @@ -169571 \ No newline at end of file +169581 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap b/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap index f1a4bb72..73c52189 100644 --- a/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap +++ b/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap @@ -1 +1 @@ -169514 \ No newline at end of file +169524 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecay.snap b/.forge-snapshots/V3-DutchDecay.snap index 284a734f..f15b865b 100644 --- a/.forge-snapshots/V3-DutchDecay.snap +++ b/.forge-snapshots/V3-DutchDecay.snap @@ -1 +1 @@ -13371 \ No newline at end of file +17903 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayBounded.snap b/.forge-snapshots/V3-DutchDecayBounded.snap index 77e48ce7..d239a2b0 100644 --- a/.forge-snapshots/V3-DutchDecayBounded.snap +++ b/.forge-snapshots/V3-DutchDecayBounded.snap @@ -1 +1 @@ -1193 \ No newline at end of file +3389 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap index e2b27312..b9bae990 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap @@ -1 +1 @@ -6779 \ No newline at end of file +9958 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap index 67e488ea..5b4e69b0 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap @@ -1 +1 @@ -6467 \ No newline at end of file +9672 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNegative.snap b/.forge-snapshots/V3-DutchDecayNegative.snap index 34201494..9cbd0e0f 100644 --- a/.forge-snapshots/V3-DutchDecayNegative.snap +++ b/.forge-snapshots/V3-DutchDecayNegative.snap @@ -1 +1 @@ -1265 \ No newline at end of file +3861 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNoDecay.snap b/.forge-snapshots/V3-DutchDecayNoDecay.snap index 4b447b70..49be3686 100644 --- a/.forge-snapshots/V3-DutchDecayNoDecay.snap +++ b/.forge-snapshots/V3-DutchDecayNoDecay.snap @@ -1 +1 @@ -5899 \ No newline at end of file +9977 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNoDecayYet.snap b/.forge-snapshots/V3-DutchDecayNoDecayYet.snap index 222192ce..6ee71bda 100644 --- a/.forge-snapshots/V3-DutchDecayNoDecayYet.snap +++ b/.forge-snapshots/V3-DutchDecayNoDecayYet.snap @@ -1 +1 @@ -4697 \ No newline at end of file +7890 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap b/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap index 222192ce..44439158 100644 --- a/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap +++ b/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap @@ -1 +1 @@ -4697 \ No newline at end of file +7864 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayRange.snap b/.forge-snapshots/V3-DutchDecayRange.snap index 34201494..9cbd0e0f 100644 --- a/.forge-snapshots/V3-DutchDecayRange.snap +++ b/.forge-snapshots/V3-DutchDecayRange.snap @@ -1 +1 @@ -1265 \ No newline at end of file +3861 \ No newline at end of file diff --git a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap index 6dffa512..f660d6fa 100644 --- a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap @@ -1 +1 @@ -88450 \ No newline at end of file +103346 \ No newline at end of file diff --git a/.forge-snapshots/V3-MultiPointDutchDecay.snap b/.forge-snapshots/V3-MultiPointDutchDecay.snap index 1a263175..9dce265a 100644 --- a/.forge-snapshots/V3-MultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-MultiPointDutchDecay.snap @@ -1 +1 @@ -26539 \ No newline at end of file +34003 \ No newline at end of file diff --git a/src/lib/NonlinearDutchDecayLib.sol b/src/lib/NonlinearDutchDecayLib.sol index bdeaa5f1..6aaaa62e 100644 --- a/src/lib/NonlinearDutchDecayLib.sol +++ b/src/lib/NonlinearDutchDecayLib.sol @@ -22,14 +22,18 @@ library NonlinearDutchDecayLib { /// @param curve The nonlinear decay curve definition /// @param startAmount The initial amount at the start of the decay /// @param decayStartBlock The absolute block number when the decay begins + /// @param minAmount The minimum amount to decay to + /// @param maxAmount The maximum amount to decay to + /// @param decayFunc The decay function to use /// @dev Expects the relativeBlocks in curve to be strictly increasing - /// @return decayedAmount The amount after applying the decay, bounded by minAmount and maxAmount + /// @return decayedAmount The decayed amount function decay( NonlinearDutchDecay memory curve, uint256 startAmount, uint256 decayStartBlock, uint256 minAmount, - uint256 maxAmount + uint256 maxAmount, + function(uint256, uint256, uint256, int256, int256) internal pure returns (int256) decayFunc ) internal view returns (uint256 decayedAmount) { // mismatch of relativeAmounts and relativeBlocks if (curve.relativeAmounts.length > 16) { @@ -46,7 +50,7 @@ library NonlinearDutchDecayLib { (uint16 startPoint, uint16 endPoint, int256 relStartAmount, int256 relEndAmount) = locateCurvePosition(curve, blockDelta); // get decay of only the relative amounts - int256 curveDelta = DutchDecayLib.linearDecay(startPoint, endPoint, blockDelta, relStartAmount, relEndAmount); + int256 curveDelta = decayFunc(startPoint, endPoint, blockDelta, relStartAmount, relEndAmount); return startAmount.boundedSub(curveDelta, minAmount, maxAmount); } @@ -100,8 +104,9 @@ library NonlinearDutchDecayLib { view returns (OutputToken memory result) { - uint256 decayedOutput = - decay(output.curve, output.startAmount, decayStartBlock, output.minAmount, type(uint256).max); + uint256 decayedOutput = decay( + output.curve, output.startAmount, decayStartBlock, output.minAmount, type(uint256).max, v3LinearOutputDecay + ); result = OutputToken(output.token, decayedOutput, output.recipient); } @@ -130,7 +135,64 @@ library NonlinearDutchDecayLib { view returns (InputToken memory result) { - uint256 decayedInput = decay(input.curve, input.startAmount, decayStartBlock, 0, input.maxAmount); + uint256 decayedInput = + decay(input.curve, input.startAmount, decayStartBlock, 0, input.maxAmount, v3LinearInputDecay); result = InputToken(input.token, decayedInput, input.maxAmount); } + + /// @notice returns the linear interpolation between the two points + /// @param startPoint The start of the decay + /// @param endPoint The end of the decay + /// @param currentPoint The current position in the decay + /// @param startAmount The amount of the start of the decay + /// @param endAmount The amount of the end of the decay + /// @dev rounds in favor of the swapper based on input or output + function v3LinearInputDecay( + uint256 startPoint, + uint256 endPoint, + uint256 currentPoint, + int256 startAmount, + int256 endAmount + ) internal pure returns (int256) { + if (currentPoint >= endPoint) { + return endAmount; + } + uint256 elapsed = currentPoint - startPoint; + uint256 duration = endPoint - startPoint; + int256 delta; + + // Because startAmount + delta is subtracted from the original amount, + // we want to maximize startAmount + delta to favor the swapper + if (endAmount < startAmount) { + delta = -int256(uint256(startAmount - endAmount).mulDivDown(elapsed, duration)); + } else { + delta = int256(uint256(endAmount - startAmount).mulDivUp(elapsed, duration)); + } + + return startAmount + delta; + } + + function v3LinearOutputDecay( + uint256 startPoint, + uint256 endPoint, + uint256 currentPoint, + int256 startAmount, + int256 endAmount + ) internal pure returns (int256) { + if (currentPoint >= endPoint) { + return endAmount; + } + uint256 elapsed = currentPoint - startPoint; + uint256 duration = endPoint - startPoint; + int256 delta; + + // For outputs, we want to minimize startAmount + delta to favor the swapper + if (endAmount < startAmount) { + delta = -int256(uint256(startAmount - endAmount).mulDivUp(elapsed, duration)); + } else { + delta = int256(uint256(endAmount - startAmount).mulDivDown(elapsed, duration)); + } + + return startAmount + delta; + } } diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index 8fa6c522..a148bd41 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -11,22 +11,37 @@ import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol"; import {Math} from "openzeppelin-contracts/utils/math/Math.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; import {CurveBuilder} from "../util/CurveBuilder.sol"; +import {MockERC20} from "../util/mock/MockERC20.sol"; +import {OutputToken, InputToken} from "../../src/base/ReactorStructs.sol"; -/// @notice mock contract to test NonlinearDutchDecayLib functionality -contract MockNonlinearDutchDecayLibContract { - function decay( +contract NonlinearDutchDecayLibTest is Test, GasSnapshot { + MockERC20 tokenIn; + MockERC20 tokenOut; + + constructor() { + tokenIn = new MockERC20("Input", "IN", 18); + tokenOut = new MockERC20("Output", "OUT", 18); + } + + function decayInput( NonlinearDutchDecay memory curve, uint256 startAmount, uint256 decayStartBlock, - uint256 minAmount, uint256 maxAmount - ) public view { - NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, minAmount, maxAmount); + ) internal view returns (uint256 decayedAmount) { + V3DutchInput memory input = V3DutchInput(tokenIn, startAmount, curve, maxAmount, 0); + return NonlinearDutchDecayLib.decay(input, decayStartBlock).amount; } -} -contract NonlinearDutchDecayLibTest is Test, GasSnapshot { - MockNonlinearDutchDecayLibContract mockNonlinearDutchDecayLibContract = new MockNonlinearDutchDecayLibContract(); + function decayOutput( + NonlinearDutchDecay memory curve, + uint256 startAmount, + uint256 decayStartBlock, + uint256 minAmount + ) internal view returns (uint256 decayedAmount) { + V3DutchOutput memory output = V3DutchOutput(address(tokenOut), startAmount, curve, address(0), minAmount, 0); + return NonlinearDutchDecayLib.decay(output, decayStartBlock).amount; + } function testLocateCurvePositionSingle() public { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(1, 0); @@ -109,19 +124,11 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { // Empty curve snapStart("V3-DutchDecayNoDecay"); - assertEq( - NonlinearDutchDecayLib.decay( - CurveBuilder.emptyCurve(), startAmount, decayStartBlock, startAmount, startAmount - ), - startAmount - ); + assertEq(decayOutput(CurveBuilder.emptyCurve(), startAmount, decayStartBlock, startAmount), startAmount); // Single value with 0 amount change assertEq( - NonlinearDutchDecayLib.decay( - CurveBuilder.singlePointCurve(1, 0), startAmount, decayStartBlock, startAmount, startAmount - ), - startAmount + decayOutput(CurveBuilder.singlePointCurve(1, 0), startAmount, decayStartBlock, startAmount), startAmount ); snapEnd(); } @@ -134,11 +141,11 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { snapStart("V3-DutchDecayNoDecayYet"); vm.roll(100); // at decayStartBlock - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, startAmount, 2 ether), startAmount); + assertEq(decayOutput(curve, startAmount, decayStartBlock, startAmount), startAmount); vm.roll(80); // before decayStartBlock - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, startAmount, 2 ether), startAmount); + assertEq(decayOutput(curve, startAmount, decayStartBlock, startAmount), startAmount); snapEnd(); } @@ -150,11 +157,11 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { snapStart("V3-DutchDecayNoDecayYetNegative"); vm.roll(100); // at decayStartBlock - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0, 1 ether), startAmount); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0), startAmount); vm.roll(80); // before decayStartBlock - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0, 1 ether), startAmount); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0), startAmount); snapEnd(); } @@ -165,19 +172,99 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); snapStart("V3-DutchDecay"); vm.roll(150); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.5 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.5 ether); vm.roll(180); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.8 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.8 ether); vm.roll(110); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.1 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.1 ether); vm.roll(190); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.9 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.9 ether); snapEnd(); } + function testDutchInputDecayRounding() public { + uint256 decayStartBlock = 0; + uint256 startAmount = 2000; + int256 decayAmount = 1000; + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(10000, decayAmount); + + vm.roll(0); + assertEq(decayInput(curve, startAmount, decayStartBlock, 3000), 2000); + + vm.roll(1); + // Input should round down to favor the swapper + assertEq(decayInput(curve, startAmount, decayStartBlock, 3000), 1999); + + vm.roll(9); + assertEq(decayInput(curve, startAmount, decayStartBlock, 3000), 1999); + } + + function testDutchOutputDecayRounding() public { + uint256 decayStartBlock = 0; + uint256 startAmount = 2000; + int256 decayAmount = 1000; + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(10000, decayAmount); + + vm.roll(0); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1000), 2000); + + vm.roll(1); + // Output should round up to favor the swapper + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1000), 2000); + + vm.roll(9); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1000), 2000); + + vm.roll(10); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1000), 1999); + } + + function testDutchInputUpwardDecayRounding() public { + uint256 decayStartBlock = 0; + uint256 startAmount = 2000; + int256 decayAmount = -1000; + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(10000, decayAmount); + + vm.roll(0); + assertEq(decayInput(curve, startAmount, decayStartBlock, 3000), 2000); + + vm.roll(1); + // Input should round down to favor the swapper + assertEq(decayInput(curve, startAmount, decayStartBlock, 3000), 2000); + + vm.roll(9); + assertEq(decayInput(curve, startAmount, decayStartBlock, 3000), 2000); + + vm.roll(10); + assertEq(decayInput(curve, startAmount, decayStartBlock, 3000), 2001); + } + + function testDutchOutputUpwardDecayRounding() public { + uint256 decayStartBlock = 0; + uint256 startAmount = 2000; + int256 decayAmount = -1000; + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(10000, decayAmount); + + vm.roll(0); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1000), 2000); + + vm.roll(1); + // Output should round up to favor the swapper + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1000), 2001); + + vm.roll(9); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1000), 2001); + + vm.roll(10); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1000), 2001); + + vm.roll(11); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1000), 2002); + } + function testDutchDecayNegative() public { uint256 decayStartBlock = 100; uint256 startAmount = 2 ether; @@ -185,16 +272,16 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); snapStart("V3-DutchDecayNegative"); vm.roll(150); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.5 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.5 ether); vm.roll(180); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.2 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.2 ether); vm.roll(110); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.9 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.9 ether); vm.roll(190); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.1 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.1 ether); snapEnd(); } @@ -205,10 +292,10 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); snapStart("V3-DutchDecayFullyDecayed"); vm.roll(200); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 2 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 2 ether); vm.warp(250); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 2 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 2 ether); snapEnd(); } @@ -219,10 +306,10 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); snapStart("V3-DutchDecayFullyDecayedNegative"); vm.roll(200); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1 ether); vm.warp(250); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1 ether); snapEnd(); } @@ -234,7 +321,7 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(decayDuration, 0 - int256(decayAmount)); snapStart("V3-DutchDecayRange"); - uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0, type(uint256).max); + uint256 decayed = decayOutput(curve, startAmount, decayStartBlock, 0); assertGe(decayed, startAmount); assertLe(decayed, startAmount + uint256(decayAmount)); snapEnd(); @@ -245,18 +332,15 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { int256 decayAmount, uint256 decayStartBlock, uint16 decayDuration, - uint256 minAmount, - uint256 maxAmount + uint256 minAmount ) public { vm.assume(decayAmount > 0); vm.assume(startAmount <= uint256(type(int256).max - decayAmount)); - vm.assume(maxAmount > minAmount); NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(decayDuration, 0 - int256(decayAmount)); snapStart("V3-DutchDecayBounded"); - uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, minAmount, maxAmount); + uint256 decayed = decayOutput(curve, startAmount, decayStartBlock, minAmount); assertGe(decayed, minAmount); - assertLe(decayed, maxAmount); snapEnd(); } @@ -274,7 +358,7 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(decayDuration, int256(decayAmount)); snapStart("V3-DutchDecayNegative"); - uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0, type(uint256).max); + uint256 decayed = decayOutput(curve, startAmount, decayStartBlock, 0); assertLe(decayed, startAmount); assertGe(decayed, startAmount - decayAmount); snapEnd(); @@ -294,31 +378,31 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); snapStart("V3-MultiPointDutchDecay"); vm.roll(50); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0 ether), 1 ether); vm.roll(150); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1.5 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0 ether), 1.5 ether); vm.roll(200); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 2 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0 ether), 2 ether); vm.roll(210); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1.9 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0 ether), 1.9 ether); vm.roll(290); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1.1 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0 ether), 1.1 ether); vm.roll(300); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0 ether), 1 ether); vm.roll(350); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 0.5 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0 ether), 0.5 ether); vm.roll(400); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 0 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0 ether), 0 ether); vm.roll(500); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 0 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0 ether), 0 ether); snapEnd(); } @@ -365,67 +449,67 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { snapStart("V3-ExtendedMultiPointDutchDecay"); vm.roll(50); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1 ether); vm.roll(150); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.05 ether); // halfway between 100 (1 ether) and 200 (1.1 ether) + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.05 ether); // halfway between 100 (1 ether) and 200 (1.1 ether) vm.roll(200); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.1 ether); // 1 + 0.1 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.1 ether); // 1 + 0.1 ether vm.roll(250); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.15 ether); // halfway between 200 (1.1 ether) and 300 (1.2 ether) + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.15 ether); // halfway between 200 (1.1 ether) and 300 (1.2 ether) vm.roll(300); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.2 ether); // 1 + 0.2 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.2 ether); // 1 + 0.2 ether vm.roll(350); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.25 ether); // halfway between 300 (1.2 ether) and 400 (1.3 ether) + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.25 ether); // halfway between 300 (1.2 ether) and 400 (1.3 ether) vm.roll(400); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.3 ether); // 1 + 0.3 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.3 ether); // 1 + 0.3 ether vm.roll(450); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.35 ether); // halfway between 400 (1.3 ether) and 500 (1.4 ether) + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.35 ether); // halfway between 400 (1.3 ether) and 500 (1.4 ether) vm.roll(500); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.4 ether); // 1 + 0.4 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.4 ether); // 1 + 0.4 ether vm.roll(600); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.5 ether); // 1 + 0.5 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.5 ether); // 1 + 0.5 ether vm.roll(700); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.6 ether); // 1 + 0.6 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.6 ether); // 1 + 0.6 ether vm.roll(800); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.7 ether); // 1 + 0.7 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.7 ether); // 1 + 0.7 ether vm.roll(900); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.8 ether); // 1 + 0.8 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.8 ether); // 1 + 0.8 ether vm.roll(1000); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.9 ether); // 1 + 0.9 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.9 ether); // 1 + 0.9 ether vm.roll(1100); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 2 ether); // 1 + 1 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 2 ether); // 1 + 1 ether vm.roll(1200); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.9 ether); // 1 + 0.9 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.9 ether); // 1 + 0.9 ether vm.roll(1300); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.8 ether); // 1 + 0.8 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.8 ether); // 1 + 0.8 ether vm.roll(1400); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.7 ether); // 1 + 0.7 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.7 ether); // 1 + 0.7 ether vm.roll(1500); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.6 ether); // 1 + 0.6 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.6 ether); // 1 + 0.6 ether vm.roll(1600); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.5 ether); // 1 + 0.5 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.5 ether); // 1 + 0.5 ether vm.roll(1650); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.45 ether); // 1 + 0.45 ether + assertEq(decayOutput(curve, startAmount, decayStartBlock, 1 ether), 1.45 ether); // 1 + 0.45 ether snapEnd(); } @@ -445,7 +529,7 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { decayAmounts[2] = 1 ether; // 0 ether NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); vm.roll(350); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 0.25 ether); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0 ether), 0.25 ether); } function testDutchDecayToNegative() public { @@ -454,7 +538,7 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { int256 decayAmount = 2 ether; NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); vm.roll(150); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 1 ether), 0); + assertEq(decayOutput(curve, startAmount, decayStartBlock, 0 ether), 0); } function testDutchOverflowDecay() public { @@ -464,7 +548,7 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); vm.roll(150); vm.expectRevert(); - mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock, 0 ether, 1 ether); + decayOutput(curve, startAmount, decayStartBlock, 1 ether); } function testDutchMismatchedDecay() public { @@ -473,13 +557,51 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(ArrayBuilder.fillUint16(16, 1), ArrayBuilder.fillInt(17, 0)); vm.expectRevert(NonlinearDutchDecayLib.InvalidDecayCurve.selector); - mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock, 1 ether, 1 ether); + decayOutput(curve, startAmount, decayStartBlock, 1 ether); + } + + function testFuzzDutchDecayInputBeyondUint16Max( + uint16 lastValidBlock, // For curve + uint256 decayAmountFuzz, // For curve + // decayInput(curve, startAmount, decayStartBlock, maxAmount); + uint256 startAmount, + uint256 decayStartBlock, + uint256 maxAmount, + uint256 currentBlock + ) public { + vm.assume(decayStartBlock < type(uint256).max - type(uint16).max); + vm.assume(lastValidBlock > 0); + vm.assume(startAmount > 0 && startAmount < uint256(type(int256).max)); + vm.assume(maxAmount >= startAmount); + // bound only takes uint256, so we need to limit decayAmountFuzz to int256.max + // because we cast it to int256 in the decay function + decayAmountFuzz = bound(decayAmountFuzz, 0, startAmount); + + // Testing that we get a fully decayed curve instead of overflowed mistake + // This will happen when the block delta is larger than type(uint16).max + vm.assume(currentBlock > decayStartBlock + type(uint16).max); + + uint16[] memory blocks = new uint16[](1); + blocks[0] = lastValidBlock; + + int256[] memory decayAmounts = new int256[](1); + decayAmounts[0] = int256(decayAmountFuzz); + + NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); + + vm.roll(currentBlock); + uint256 decayed = decayInput(curve, startAmount, decayStartBlock, maxAmount); + assertEq( + decayed, + Math.min(startAmount - decayAmountFuzz, maxAmount), + "Should be fully decayed for block delta beyond uint16.max" + ); } - function testFuzzDutchDecayBeyondUint16Max( + function testFuzzDutchDecayOutputBeyondUint16Max( uint16 lastValidBlock, // For curve uint256 decayAmountFuzz, // For curve - // decay(curve, startAmount, decayStartBlock, minAmount, maxAmount); + // decayOutput(curve, startAmount, decayStartBlock, minAmount); uint256 startAmount, uint256 decayStartBlock, uint256 minAmount, @@ -508,7 +630,7 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); vm.roll(currentBlock); - uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, minAmount, maxAmount); + uint256 decayed = decayOutput(curve, startAmount, decayStartBlock, minAmount); assertEq( decayed, Math.max(startAmount - decayAmountFuzz, minAmount), diff --git a/test/reactors/V3DutchOrderReactor.t.sol b/test/reactors/V3DutchOrderReactor.t.sol index 694cca03..8b1e57e0 100644 --- a/test/reactors/V3DutchOrderReactor.t.sol +++ b/test/reactors/V3DutchOrderReactor.t.sol @@ -679,7 +679,8 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { assertEq(resolvedOrder.input.amount, 0); } - // 1000 - (100 * (1659087340-1659029740) / (65535)) = 913 + // 1000 - (100 * (1659087340-1659029740) / (65535)) = 912.1 + // This is the output, which should round up to favor the swapper: 913 function testV3ResolveEndBlockAfterNow() public { uint256 currentBlock = 1659087340; uint16 relativeEndBlock = 65535; @@ -702,6 +703,100 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { assertEq(resolvedOrder.input.amount, 0); } + // 1000 - (100 * (1659087340-1659029740) / (65535)) = 912.1 + // This is the input, which should round down to favor the swapper: 912 + function testV3ResolveInputEndBlockAfterNow() public { + uint256 currentBlock = 1659087340; + uint16 relativeEndBlock = 65535; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: 1659029740, + deadline: 1659130540, + input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(relativeEndBlock, 100), 1000, 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 0, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.input.amount, 912); + assertEq(resolvedOrder.outputs.length, 1); + assertEq(resolvedOrder.outputs[0].amount, 0); + } + + // 1000 - (100 * (1659087340-1659029740) / (65535)) = 912.1 + // This is the output, which should round up to favor the swapper: 913 + function testV3ResolveOutputEndBlockAfterNow() public { + uint256 currentBlock = 1659087340; + uint16 relativeEndBlock = 65535; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: 1659029740, + deadline: 1659130540, + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0, 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1000, 900, CurveBuilder.singlePointCurve(relativeEndBlock, 100), address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 913); + assertEq(resolvedOrder.input.amount, 0); + } + + // 1000 - (-100 * (1659087340-1659029740) / (65535)) = 1087.89... + // This is the input, which should round down to favor the swapper: 1087 + function testV3ResolvePositiveSlopeInputEndBlockAfterNow() public { + uint256 currentBlock = 1659087340; + uint16 relativeEndBlock = 65535; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: 1659029740, + deadline: 1659130540, + input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(relativeEndBlock, -100), 1100, 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 0, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.input.amount, 1087); + assertEq(resolvedOrder.outputs.length, 1); + assertEq(resolvedOrder.outputs[0].amount, 0); + } + + // 1000 - (-100 * (1659087340-1659029740) / (65535)) = 1087.89... + // This is the output, which should round up to favor the swapper: 1088 + function testV3ResolvePositiveSlopeOutputEndBlockAfterNow() public { + uint256 currentBlock = 1659087340; + uint16 relativeEndBlock = 65535; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: 1659029740, + deadline: 1659130540, + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0, 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1000, 1000, CurveBuilder.singlePointCurve(relativeEndBlock, -100), address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1088); + assertEq(resolvedOrder.input.amount, 0); + } + // Test multiple dutch outputs get resolved correctly. function testV3ResolveMultipleDutchOutputs() public { uint256 currentBlock = 1659087340; @@ -754,11 +849,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { assertEq(resolvedOrder.input.amount, 0); } - // At block 99, output will still be 1000. One block later at 100 (1% of 10k), + // At block 99, output will still be 1000. One block later at 100, // the first decay will occur and the output will be 999. + // This is because it is the output, which should round up + // to favor the swapper (999.01... -> 1000) function testV3ResolveFirstDecay() public { uint256 startBlock = 0; - uint256 currentBlock = 99; // 1659030395 - 1659029740 = 655 = 0.00999 * 65535 + uint256 currentBlock = 99; uint16 relativeEndBlock = 10000; vm.roll(currentBlock);