diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap index 06eecbc6..33bf6499 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -181708 \ No newline at end of file +181794 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap index 75b650c5..9f08d23a 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -196661 \ No newline at end of file +196841 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap index 9cff91c6..33f65067 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -206312 \ No newline at end of file +206492 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index 8eb9b64e..51eeba6b 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -259868 \ No newline at end of file +260048 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap index e2cee2b1..3ae6d7d9 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -190187 \ No newline at end of file +190367 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap index 462e130b..57918cda 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -148090 \ No newline at end of file +148180 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap index cf9cd5b1..e04bfbcd 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -133657 \ No newline at end of file +133747 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap index 3606dddc..16e736c7 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -157405 \ No newline at end of file +157495 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-RevertInvalidNonce.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-RevertInvalidNonce.snap index 106fc79c..efa56cd6 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-RevertInvalidNonce.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-RevertInvalidNonce.snap @@ -1 +1 @@ -26649 \ No newline at end of file +26739 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-V2DutchOrder-BaseExecuteSingleWithFee.snap index 4b1517ec..e10d096e 100644 --- a/.forge-snapshots/Base-V2DutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-V2DutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -188493 \ No newline at end of file +188579 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExclusiveFiller.snap b/.forge-snapshots/Base-V2DutchOrder-ExclusiveFiller.snap index 3f77dc49..3efb8afe 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExclusiveFiller.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExclusiveFiller.snap @@ -1 +1 @@ -158636 \ No newline at end of file +158726 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatch.snap index 99cea989..a36a72e8 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -210334 \ No newline at end of file +210514 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputs.snap index ce9c1f0b..5bd26769 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -220559 \ No newline at end of file +220739 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index 63d45438..a9b7a4b0 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -274686 \ No newline at end of file +274866 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchNativeOutput.snap index a2f04c92..fcd794f1 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -203860 \ No newline at end of file +204040 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingle.snap index ab27c01b..ddc8d048 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -154872 \ No newline at end of file +154962 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleNativeOutput.snap index 8499cad8..2ed6ee49 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -140434 \ No newline at end of file +140524 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleValidation.snap index 2218578a..85b93caf 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -164183 \ No newline at end of file +164273 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-InputOverride.snap b/.forge-snapshots/Base-V2DutchOrder-InputOverride.snap index 37745aea..100cb528 100644 --- a/.forge-snapshots/Base-V2DutchOrder-InputOverride.snap +++ b/.forge-snapshots/Base-V2DutchOrder-InputOverride.snap @@ -1 +1 @@ -158714 \ No newline at end of file +158804 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-OutputOverride.snap b/.forge-snapshots/Base-V2DutchOrder-OutputOverride.snap index dea3ba88..446c64c6 100644 --- a/.forge-snapshots/Base-V2DutchOrder-OutputOverride.snap +++ b/.forge-snapshots/Base-V2DutchOrder-OutputOverride.snap @@ -1 +1 @@ -158663 \ No newline at end of file +158753 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-RevertInvalidNonce.snap b/.forge-snapshots/Base-V2DutchOrder-RevertInvalidNonce.snap index bdfe68ee..b613387f 100644 --- a/.forge-snapshots/Base-V2DutchOrder-RevertInvalidNonce.snap +++ b/.forge-snapshots/Base-V2DutchOrder-RevertInvalidNonce.snap @@ -1 +1 @@ -33446 \ No newline at end of file +33536 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap new file mode 100644 index 00000000..a4f7c27f --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap @@ -0,0 +1 @@ +198132 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap new file mode 100644 index 00000000..5a9bc93c --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap @@ -0,0 +1 @@ +229797 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap new file mode 100644 index 00000000..689ecd8a --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap @@ -0,0 +1 @@ +243298 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap new file mode 100644 index 00000000..1deec62d --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -0,0 +1 @@ +300708 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap new file mode 100644 index 00000000..00eab807 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap @@ -0,0 +1 @@ +223323 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap new file mode 100644 index 00000000..5f3411e0 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap @@ -0,0 +1 @@ +164517 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap new file mode 100644 index 00000000..76a79751 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap @@ -0,0 +1 @@ +150079 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap new file mode 100644 index 00000000..3b1435ad --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap @@ -0,0 +1 @@ +173829 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-InputOverride.snap b/.forge-snapshots/Base-V3DutchOrder-InputOverride.snap new file mode 100644 index 00000000..bda8f296 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-InputOverride.snap @@ -0,0 +1 @@ +163131 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap b/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap new file mode 100644 index 00000000..f497df92 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap @@ -0,0 +1 @@ +43133 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap b/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap new file mode 100644 index 00000000..0383dc25 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap @@ -0,0 +1 @@ +168443 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap b/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap new file mode 100644 index 00000000..824d4c13 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap @@ -0,0 +1 @@ +168524 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap b/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap new file mode 100644 index 00000000..c11c0243 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap @@ -0,0 +1 @@ +168467 \ No newline at end of file diff --git a/.forge-snapshots/EthOutputTestEthOutput.snap b/.forge-snapshots/EthOutputTestEthOutput.snap index 454b0839..b1321526 100644 --- a/.forge-snapshots/EthOutputTestEthOutput.snap +++ b/.forge-snapshots/EthOutputTestEthOutput.snap @@ -1 +1 @@ -155866 \ No newline at end of file +156451 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap index c0977a90..2c352e02 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap @@ -1 +1 @@ -176211 \ No newline at end of file +176297 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap index 4b4dcb44..86d87ba4 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap @@ -1 +1 @@ -162203 \ No newline at end of file +162289 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap index ef1eb938..591acb7c 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap @@ -1 +1 @@ -165488 \ No newline at end of file +165574 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap index 4d6186b5..81370684 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap @@ -1 +1 @@ -146315 \ No newline at end of file +146401 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap index b5ee7c88..17ffcf5f 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap @@ -1 +1 @@ -148895 \ No newline at end of file +148981 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap index 44bd11d7..324315d8 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap @@ -1 +1 @@ -124557 \ No newline at end of file +124643 \ No newline at end of file diff --git a/.forge-snapshots/SwapRouter02ExecutorExecute.snap b/.forge-snapshots/SwapRouter02ExecutorExecute.snap index c31b0cf2..b48d97a4 100644 --- a/.forge-snapshots/SwapRouter02ExecutorExecute.snap +++ b/.forge-snapshots/SwapRouter02ExecutorExecute.snap @@ -1 +1 @@ -262691 \ No newline at end of file +263276 \ No newline at end of file diff --git a/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap b/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap index cb0f5e97..d8dbb860 100644 --- a/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap +++ b/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap @@ -1 +1 @@ -117810 \ No newline at end of file +118395 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecay.snap b/.forge-snapshots/V3-DutchDecay.snap new file mode 100644 index 00000000..f7ed1714 --- /dev/null +++ b/.forge-snapshots/V3-DutchDecay.snap @@ -0,0 +1 @@ +20920 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayBounded.snap b/.forge-snapshots/V3-DutchDecayBounded.snap new file mode 100644 index 00000000..7862d759 --- /dev/null +++ b/.forge-snapshots/V3-DutchDecayBounded.snap @@ -0,0 +1 @@ +1209 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap new file mode 100644 index 00000000..d20b1a84 --- /dev/null +++ b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap @@ -0,0 +1 @@ +11797 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap new file mode 100644 index 00000000..fd311904 --- /dev/null +++ b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap @@ -0,0 +1 @@ +11485 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNegative.snap b/.forge-snapshots/V3-DutchDecayNegative.snap new file mode 100644 index 00000000..11b9a892 --- /dev/null +++ b/.forge-snapshots/V3-DutchDecayNegative.snap @@ -0,0 +1 @@ +1271 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNoDecay.snap b/.forge-snapshots/V3-DutchDecayNoDecay.snap new file mode 100644 index 00000000..4b447b70 --- /dev/null +++ b/.forge-snapshots/V3-DutchDecayNoDecay.snap @@ -0,0 +1 @@ +5899 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNoDecayYet.snap b/.forge-snapshots/V3-DutchDecayNoDecayYet.snap new file mode 100644 index 00000000..9b08816d --- /dev/null +++ b/.forge-snapshots/V3-DutchDecayNoDecayYet.snap @@ -0,0 +1 @@ +4703 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap b/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap new file mode 100644 index 00000000..9b08816d --- /dev/null +++ b/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap @@ -0,0 +1 @@ +4703 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayRange.snap b/.forge-snapshots/V3-DutchDecayRange.snap new file mode 100644 index 00000000..11b9a892 --- /dev/null +++ b/.forge-snapshots/V3-DutchDecayRange.snap @@ -0,0 +1 @@ +1271 \ No newline at end of file diff --git a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap new file mode 100644 index 00000000..2fff3d5a --- /dev/null +++ b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap @@ -0,0 +1 @@ +209206 \ No newline at end of file diff --git a/.forge-snapshots/V3-LocateCurvePositionMulti.snap b/.forge-snapshots/V3-LocateCurvePositionMulti.snap new file mode 100644 index 00000000..1f9b6835 --- /dev/null +++ b/.forge-snapshots/V3-LocateCurvePositionMulti.snap @@ -0,0 +1 @@ +40525 \ No newline at end of file diff --git a/.forge-snapshots/V3-LocateCurvePositionSingle.snap b/.forge-snapshots/V3-LocateCurvePositionSingle.snap new file mode 100644 index 00000000..e274bed6 --- /dev/null +++ b/.forge-snapshots/V3-LocateCurvePositionSingle.snap @@ -0,0 +1 @@ +12623 \ No newline at end of file diff --git a/.forge-snapshots/V3-MultiPointDutchDecay.snap b/.forge-snapshots/V3-MultiPointDutchDecay.snap new file mode 100644 index 00000000..9015ad25 --- /dev/null +++ b/.forge-snapshots/V3-MultiPointDutchDecay.snap @@ -0,0 +1 @@ +48878 \ No newline at end of file diff --git a/src/lib/DutchDecayLib.sol b/src/lib/DutchDecayLib.sol index f6b4d551..755a97ea 100644 --- a/src/lib/DutchDecayLib.sol +++ b/src/lib/DutchDecayLib.sol @@ -37,16 +37,51 @@ library DutchDecayLib { } else if (decayStartTime >= block.timestamp) { decayedAmount = startAmount; } else { - unchecked { - uint256 elapsed = block.timestamp - decayStartTime; - uint256 duration = decayEndTime - decayStartTime; - if (endAmount < startAmount) { - decayedAmount = startAmount - (startAmount - endAmount).mulDivDown(elapsed, duration); - } else { - decayedAmount = startAmount + (endAmount - startAmount).mulDivUp(elapsed, duration); - } - } + decayedAmount = linearDecay(decayStartTime, decayEndTime, block.timestamp, startAmount, endAmount); + } + } + + /// @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 + function linearDecay( + uint256 startPoint, + uint256 endPoint, + uint256 currentPoint, + uint256 startAmount, + uint256 endAmount + ) internal pure returns (uint256) { + return uint256(linearDecay(startPoint, endPoint, currentPoint, int256(startAmount), int256(endAmount))); + } + + /// @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 + function linearDecay( + 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; + if (endAmount < startAmount) { + delta = -int256(uint256(startAmount - endAmount).mulDivDown(elapsed, duration)); + } else { + delta = int256(uint256(endAmount - startAmount).mulDivDown(elapsed, duration)); } + return startAmount + delta; } /// @notice returns a decayed output using the given dutch spec and times diff --git a/src/lib/ExclusivityLib.sol b/src/lib/ExclusivityLib.sol index 35ef7ff7..81eb6375 100644 --- a/src/lib/ExclusivityLib.sol +++ b/src/lib/ExclusivityLib.sol @@ -20,16 +20,46 @@ library ExclusivityLib { /// @notice Applies exclusivity override to the resolved order if necessary /// @param order The order to apply exclusivity override to /// @param exclusive The exclusive address - /// @param exclusivityEndTime The exclusivity end time + /// @param exclusivityEnd The exclusivity end time /// @param exclusivityOverrideBps The exclusivity override BPS - function handleExclusiveOverride( + function handleExclusiveOverrideTimestamp( ResolvedOrder memory order, address exclusive, - uint256 exclusivityEndTime, + uint256 exclusivityEnd, uint256 exclusivityOverrideBps + ) internal view { + _handleExclusiveOverride(order, exclusive, exclusivityEnd, exclusivityOverrideBps, block.timestamp); + } + + /// @notice Applies exclusivity override to the resolved order if necessary + /// @param order The order to apply exclusivity override to + /// @param exclusive The exclusive address + /// @param exclusivityEnd The exclusivity end block number + /// @param exclusivityOverrideBps The exclusivity override BPS + function handleExclusiveOverrideBlock( + ResolvedOrder memory order, + address exclusive, + uint256 exclusivityEnd, + uint256 exclusivityOverrideBps + ) internal view { + _handleExclusiveOverride(order, exclusive, exclusivityEnd, exclusivityOverrideBps, block.number); + } + + /// @notice Applies exclusivity override to the resolved order if necessary + /// @param order The order to apply exclusivity override to + /// @param exclusive The exclusive address + /// @param exclusivityEnd The exclusivity end timestamp or block number + /// @param exclusivityOverrideBps The exclusivity override BPS + /// @param currentPosition The block timestamp or number to determine exclusivity + function _handleExclusiveOverride( + ResolvedOrder memory order, + address exclusive, + uint256 exclusivityEnd, + uint256 exclusivityOverrideBps, + uint256 currentPosition ) internal view { // if the filler has fill right, we proceed with the order as-is - if (hasFillingRights(exclusive, exclusivityEndTime)) { + if (hasFillingRights(exclusive, exclusivityEnd, currentPosition)) { return; } @@ -51,10 +81,17 @@ library ExclusivityLib { } /// @notice checks if the caller currently has filling rights on the order + /// @param exclusive The exclusive address + /// @param exclusivityEnd The exclusivity end timestamp or block number + /// @param currentPosition The timestamp or block number to determine exclusivity /// @dev if the order has no exclusivity, always returns true /// @dev if the order has active exclusivity and the current filler is the exclusive address, returns true /// @dev if the order has active exclusivity and the current filler is not the exclusive address, returns false - function hasFillingRights(address exclusive, uint256 exclusivityEndTime) internal view returns (bool) { - return exclusive == address(0) || block.timestamp > exclusivityEndTime || exclusive == msg.sender; + function hasFillingRights(address exclusive, uint256 exclusivityEnd, uint256 currentPosition) + internal + view + returns (bool) + { + return exclusive == address(0) || currentPosition > exclusivityEnd || exclusive == msg.sender; } } diff --git a/src/lib/MathExt.sol b/src/lib/MathExt.sol new file mode 100644 index 00000000..02a2a786 --- /dev/null +++ b/src/lib/MathExt.sol @@ -0,0 +1,77 @@ +pragma solidity ^0.8.0; + +import {SafeCast} from "openzeppelin-contracts/utils/math/SafeCast.sol"; +import {Math} from "openzeppelin-contracts/utils/math/Math.sol"; + +library MathExt { + /// @notice Subtracts an `int256` value from a `uint256` value and returns the result. + /// @param a The unsigned integer from which the value is subtracted. + /// @param b The signed integer to subtract or add. + /// @return The result of the subtraction or addition. + function sub(uint256 a, int256 b) internal pure returns (uint256) { + if (b < 0) { + // If b is negative, add its absolute value to a + return a + uint256(-b); + } else { + return a - uint256(b); + } + } + + /// @notice Adds a signed integer `b` to an unsigned integer `a`, ensuring the result is within the specified bounds. + /// @param a The base unsigned integer. + /// @param b The signed integer to be added or subtracted. + /// @param min The minimum bound for the result. + /// @param max The maximum bound for the result. + /// @return r The result of the bounded addition. + function boundedAdd(uint256 a, int256 b, uint256 min, uint256 max) internal pure returns (uint256 r) { + r = boundedSub(a, 0 - b, min, max); + } + + /// @notice Subtracts or adds a signed integer `b` from an unsigned integer `a`, ensuring the result is within the specified bounds. + /// @param a The base unsigned integer. + /// @param b The signed integer to be subtracted or added. + /// @param min The minimum bound for the result. + /// @param max The maximum bound for the result. + /// @return r The result of the bounded subtraction. + function boundedSub(uint256 a, int256 b, uint256 min, uint256 max) internal pure returns (uint256 r) { + if (b < 0) { + // If b is negative, add its absolute value to a + uint256 absB = uint256(-b); + // would overflow + if (type(uint256).max - absB < a) { + return max; + } + r = a + absB; + } else { + // If b is positive, subtract it from a + if (a < uint256(b)) { + // cap it at min + return min; + } + + r = a - uint256(b); + } + r = bound(r, min, max); + } + + /// @notice Subtracts a `uint256` value `b` from another `uint256` value `a`, returning the result as an `int256`. + /// @param a The unsigned integer to subtract from. + /// @param b The unsigned integer to subtract. + /// @return The result of the subtraction as a signed integer. + function sub(uint256 a, uint256 b) internal pure returns (int256) { + if (a < b) { + return 0 - SafeCast.toInt256(b - a); + } else { + return SafeCast.toInt256(a - b); + } + } + + /// @notice Bounds a uint value between a minimum and maximum value. + /// @param value The value to be bounded. + /// @param min The minimum value allowed. + /// @param max The maximum value allowed. + /// @return The bounded value. + function bound(uint256 value, uint256 min, uint256 max) internal pure returns (uint256) { + return Math.min(Math.max(value, min), max); + } +} diff --git a/src/lib/NonlinearDutchDecayLib.sol b/src/lib/NonlinearDutchDecayLib.sol new file mode 100644 index 00000000..b979a883 --- /dev/null +++ b/src/lib/NonlinearDutchDecayLib.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {OutputToken, InputToken} from "../base/ReactorStructs.sol"; +import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../lib/V3DutchOrderLib.sol"; +import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; +import {MathExt} from "./MathExt.sol"; +import {Uint16ArrayLibrary, Uint16Array, fromUnderlying} from "../types/Uint16Array.sol"; +import {DutchDecayLib} from "./DutchDecayLib.sol"; + +/// @notice helpers for handling non-linear dutch order objects +library NonlinearDutchDecayLib { + using FixedPointMathLib for uint256; + using MathExt for uint256; + using Uint16ArrayLibrary for Uint16Array; + + /// @notice thrown when the decay curve is invalid + error InvalidDecayCurve(); + + /// @notice locates the current position on the curve and calculates the decay + /// @param curve The curve to search + /// @param startAmount The absolute start amount + /// @param decayStartBlock The absolute start block of the decay + /// @dev Expects the relativeBlocks in curve to be strictly increasing + function decay( + NonlinearDutchDecay memory curve, + uint256 startAmount, + uint256 decayStartBlock, + uint256 minAmount, + uint256 maxAmount + ) internal view returns (uint256 decayedAmount) { + // mismatch of relativeAmounts and relativeBlocks + if (curve.relativeAmounts.length > 16) { + revert InvalidDecayCurve(); + } + + // handle current block before decay or no decay + if (decayStartBlock >= block.number || curve.relativeAmounts.length == 0) { + return startAmount.bound(minAmount, maxAmount); + } + + uint16 blockDelta = uint16(block.number - decayStartBlock); + (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); + + return startAmount.boundedSub(curveDelta, minAmount, maxAmount); + } + + /// @notice Locates the current position on the curve + /// @param curve The curve to search + /// @param currentRelativeBlock The current relative position + /// @return startPoint The relative block before the current position + /// @return endPoint The relative block after the current position + /// @return startAmount The relative amount before the current position + /// @return endAmount The relative amount after the current position + function locateCurvePosition(NonlinearDutchDecay memory curve, uint16 currentRelativeBlock) + internal + pure + returns (uint16 startPoint, uint16 endPoint, int256 startAmount, int256 endAmount) + { + Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); + // Position is before the start of the curve + if (relativeBlocks.getElement(0) >= currentRelativeBlock) { + return (0, relativeBlocks.getElement(0), 0, curve.relativeAmounts[0]); + } + uint16 lastCurveIndex = uint16(curve.relativeAmounts.length) - 1; + for (uint16 i = 1; i <= lastCurveIndex; i++) { + if (relativeBlocks.getElement(i) >= currentRelativeBlock) { + return ( + relativeBlocks.getElement(i - 1), + relativeBlocks.getElement(i), + curve.relativeAmounts[i - 1], + curve.relativeAmounts[i] + ); + } + } + + return ( + relativeBlocks.getElement(lastCurveIndex), + relativeBlocks.getElement(lastCurveIndex), + curve.relativeAmounts[lastCurveIndex], + curve.relativeAmounts[lastCurveIndex] + ); + } + + /// @notice returns a decayed output using the given dutch spec and blocks + /// @param output The output to decay + /// @param decayStartBlock The block to start decaying + /// @return result a decayed output + function decay(V3DutchOutput memory output, uint256 decayStartBlock) + internal + view + returns (OutputToken memory result) + { + uint256 decayedOutput = + decay(output.curve, output.startAmount, decayStartBlock, output.minAmount, type(uint256).max); + result = OutputToken(output.token, decayedOutput, output.recipient); + } + + /// @notice returns a decayed output array using the given dutch spec and blocks + /// @param outputs The output array to decay + /// @param decayStartBlock The block to start decaying + /// @return result a decayed output array + function decay(V3DutchOutput[] memory outputs, uint256 decayStartBlock) + internal + view + returns (OutputToken[] memory result) + { + uint256 outputLength = outputs.length; + result = new OutputToken[](outputLength); + for (uint256 i = 0; i < outputLength; i++) { + result[i] = decay(outputs[i], decayStartBlock); + } + } + + /// @notice returns a decayed input using the given dutch spec and times + /// @param input The input to decay + /// @param decayStartBlock The block to start decaying + /// @return result a decayed input + function decay(V3DutchInput memory input, uint256 decayStartBlock) + internal + view + returns (InputToken memory result) + { + uint256 decayedInput = decay(input.curve, input.startAmount, decayStartBlock, 0, input.maxAmount); + result = InputToken(input.token, decayedInput, input.maxAmount); + } +} diff --git a/src/lib/V3DutchOrderLib.sol b/src/lib/V3DutchOrderLib.sol new file mode 100644 index 00000000..b9f7ac48 --- /dev/null +++ b/src/lib/V3DutchOrderLib.sol @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {DutchOrderLib} from "./DutchOrderLib.sol"; +import {OrderInfo} from "../base/ReactorStructs.sol"; +import {OrderInfoLib} from "./OrderInfoLib.sol"; +import {ERC20} from "solmate/src/tokens/ERC20.sol"; + +struct CosignerData { + // The block at which the input or outputs start decaying + uint256 decayStartBlock; + // The address who has exclusive rights to the order until decayStartBlock + address exclusiveFiller; + // The amount in bps that a non-exclusive filler needs to improve the outputs by to be able to fill the order + uint256 exclusivityOverrideBps; + // The tokens that the swapper will provide when settling the order + uint256 inputAmount; + // The tokens that must be received to satisfy the order + uint256[] outputAmounts; +} + +struct V3DutchOrder { + // generic order information + OrderInfo info; + // The address which must cosign the full order + address cosigner; + // Adjust the startAmount to account for changes to gas + uint256 startingBaseFee; + // The tokens that the swapper will provide when settling the order + V3DutchInput baseInput; + // The tokens that must be received to satisfy the order + V3DutchOutput[] baseOutputs; + // signed over by the cosigner + CosignerData cosignerData; + // signature from the cosigner over (orderHash || cosignerData) + bytes cosignature; +} + +/// @dev The changes in tokens (positive or negative) to subtract from the start amount +/// @dev The relativeBlocks should be strictly increasing +struct NonlinearDutchDecay { + // 16 uint16 values packed + // Can represent curves with points 2^16 blocks into the future + uint256 relativeBlocks; + int256[] relativeAmounts; +} + +/// @dev An amount of input tokens that increases non-linearly over time +struct V3DutchInput { + // The ERC20 token address + ERC20 token; + // The amount of tokens at the starting block + uint256 startAmount; + // The amount of tokens at the each future block + NonlinearDutchDecay curve; + // The max amount of the curve + uint256 maxAmount; + // The amount of token to change per wei change in basefee + uint256 adjustmentPerGweiBaseFee; +} + +/// @dev An amount of output tokens that decreases non-linearly over time +struct V3DutchOutput { + // The ERC20 token address (or native ETH address) + address token; + // The amount of tokens at the start of the time period + uint256 startAmount; + // The amount of tokens at the each future block + NonlinearDutchDecay curve; + // The address who must receive the tokens to satisfy the order + address recipient; + // The min amount of the curve + uint256 minAmount; + // The amount of token to change per wei change in basefee + uint256 adjustmentPerGweiBaseFee; +} + +/// @notice helpers for handling custom curve order objects +library V3DutchOrderLib { + using OrderInfoLib for OrderInfo; + + bytes internal constant V3_DUTCH_ORDER_TYPE = abi.encodePacked( + "V3DutchOrder(", + "OrderInfo info,", + "address cosigner,", + "uint256 startingBaseFee,", + "V3DutchInput baseInput,", + "V3DutchOutput[] baseOutputs)" + ); + bytes internal constant V3_DUTCH_INPUT_TYPE = abi.encodePacked( + "V3DutchInput(", + "address token,", + "uint256 startAmount,", + "NonlinearDutchDecay curve,", + "uint256 maxAmount,", + "uint256 adjustmentPerGweiBaseFee)" + ); + bytes32 internal constant V3_DUTCH_INPUT_TYPE_HASH = keccak256(V3_DUTCH_INPUT_TYPE); + bytes internal constant V3_DUTCH_OUTPUT_TYPE = abi.encodePacked( + "V3DutchOutput(", + "address token,", + "uint256 startAmount,", + "NonlinearDutchDecay curve,", + "address recipient,", + "uint256 minAmount,", + "uint256 adjustmentPerGweiBaseFee)" + ); + bytes32 internal constant V3_DUTCH_OUTPUT_TYPE_HASH = keccak256(V3_DUTCH_OUTPUT_TYPE); + bytes internal constant NON_LINEAR_DECAY_TYPE = + abi.encodePacked("NonlinearDutchDecay(", "uint256 relativeBlocks,", "int256[] relativeAmounts)"); + bytes32 internal constant NON_LINEAR_DECAY_TYPE_HASH = keccak256(NON_LINEAR_DECAY_TYPE); + + bytes internal constant ORDER_TYPE = + abi.encodePacked(NON_LINEAR_DECAY_TYPE, V3_DUTCH_ORDER_TYPE, V3_DUTCH_OUTPUT_TYPE, OrderInfoLib.ORDER_INFO_TYPE); + bytes32 internal constant ORDER_TYPE_HASH = keccak256(ORDER_TYPE); + + /// @dev Note that sub-structs have to be defined in alphabetical order in the EIP-712 spec + string internal constant PERMIT2_ORDER_TYPE = string( + abi.encodePacked( + "V3DutchOrder witness)", + NON_LINEAR_DECAY_TYPE, + OrderInfoLib.ORDER_INFO_TYPE, + DutchOrderLib.TOKEN_PERMISSIONS_TYPE, + V3_DUTCH_INPUT_TYPE, + V3_DUTCH_ORDER_TYPE, + V3_DUTCH_OUTPUT_TYPE + ) + ); + + function hash(NonlinearDutchDecay memory curve) internal pure returns (bytes32) { + return keccak256( + abi.encode( + NON_LINEAR_DECAY_TYPE_HASH, curve.relativeBlocks, keccak256(abi.encodePacked(curve.relativeAmounts)) + ) + ); + } + + /// @notice hash the given input + /// @param input the input to hash + /// @return the eip-712 input hash + function hash(V3DutchInput memory input) internal pure returns (bytes32) { + return keccak256( + abi.encode( + V3_DUTCH_INPUT_TYPE_HASH, + input.token, + input.startAmount, + hash(input.curve), + input.maxAmount, + input.adjustmentPerGweiBaseFee + ) + ); + } + + /// @notice hash the given output + /// @param output the output to hash + /// @return the eip-712 output hash + function hash(V3DutchOutput memory output) internal pure returns (bytes32) { + return keccak256( + abi.encode( + V3_DUTCH_OUTPUT_TYPE_HASH, + output.token, + output.startAmount, + hash(output.curve), + output.recipient, + output.minAmount, + output.adjustmentPerGweiBaseFee + ) + ); + } + + /// @notice hash the given outputs + /// @param outputs the outputs to hash + /// @return the eip-712 outputs hash + function hash(V3DutchOutput[] memory outputs) internal pure returns (bytes32) { + unchecked { + bytes memory packedHashes = new bytes(32 * outputs.length); + uint256 outputsLength = outputs.length; + for (uint256 i = 0; i < outputsLength; i++) { + bytes32 outputHash = hash(outputs[i]); + assembly { + mstore(add(add(packedHashes, 0x20), mul(i, 0x20)), outputHash) + } + } + + return keccak256(packedHashes); + } + } + + /// @notice hash the given order + /// @param order the order to hash + /// @return the eip-712 order hash + function hash(V3DutchOrder memory order) internal pure returns (bytes32) { + return keccak256( + abi.encode( + ORDER_TYPE_HASH, + order.info.hash(), + order.cosigner, + order.startingBaseFee, + hash(order.baseInput), + hash(order.baseOutputs) + ) + ); + } + + /// @notice get the digest of the cosigner data + /// @param order the priorityOrder + /// @param orderHash the hash of the order + function cosignerDigest(V3DutchOrder memory order, bytes32 orderHash) internal view returns (bytes32) { + return keccak256(abi.encodePacked(orderHash, abi.encode(order.cosignerData))); + } +} diff --git a/src/reactors/ExclusiveDutchOrderReactor.sol b/src/reactors/ExclusiveDutchOrderReactor.sol index b52a2f74..53a3c7cb 100644 --- a/src/reactors/ExclusiveDutchOrderReactor.sol +++ b/src/reactors/ExclusiveDutchOrderReactor.sol @@ -43,7 +43,9 @@ contract ExclusiveDutchOrderReactor is BaseReactor { sig: signedOrder.sig, hash: order.hash() }); - resolvedOrder.handleExclusiveOverride(order.exclusiveFiller, order.decayStartTime, order.exclusivityOverrideBps); + resolvedOrder.handleExclusiveOverrideTimestamp( + order.exclusiveFiller, order.decayStartTime, order.exclusivityOverrideBps + ); } /// @inheritdoc BaseReactor diff --git a/src/reactors/V2DutchOrderReactor.sol b/src/reactors/V2DutchOrderReactor.sol index a4d1c9b3..a7a9e3a8 100644 --- a/src/reactors/V2DutchOrderReactor.sol +++ b/src/reactors/V2DutchOrderReactor.sol @@ -61,7 +61,7 @@ contract V2DutchOrderReactor is BaseReactor { sig: signedOrder.sig, hash: orderHash }); - resolvedOrder.handleExclusiveOverride( + resolvedOrder.handleExclusiveOverrideTimestamp( order.cosignerData.exclusiveFiller, order.cosignerData.decayStartTime, order.cosignerData.exclusivityOverrideBps diff --git a/src/reactors/V3DutchOrderReactor.sol b/src/reactors/V3DutchOrderReactor.sol new file mode 100644 index 00000000..e9d88914 --- /dev/null +++ b/src/reactors/V3DutchOrderReactor.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {BaseReactor} from "./BaseReactor.sol"; +import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; +import {Permit2Lib} from "../lib/Permit2Lib.sol"; +import {ExclusivityLib} from "../lib/ExclusivityLib.sol"; +import {NonlinearDutchDecayLib} from "../lib/NonlinearDutchDecayLib.sol"; +import {V3DutchOrderLib, V3DutchOrder, CosignerData, V3DutchOutput, V3DutchInput} from "../lib/V3DutchOrderLib.sol"; +import {SignedOrder, ResolvedOrder} from "../base/ReactorStructs.sol"; +import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; +import {MathExt} from "../lib/MathExt.sol"; +import {Math} from "openzeppelin-contracts/utils/math/Math.sol"; +import {CosignerLib} from "../lib/CosignerLib.sol"; + +/// @notice Reactor for V3 dutch orders +/// @dev V3 orders must be cosigned by the specified cosigner to override starting block and value +/// @dev resolution behavior: +/// - If cosignature is invalid or not from specified cosigner, revert +/// - If inputAmount is 0, then use baseInput +/// - If inputAmount is nonzero, then ensure it is less than specified baseInput and replace startAmount +/// - For each outputAmount: +/// - If amount is 0, then use baseOutput +/// - If amount is nonzero, then ensure it is greater than specified baseOutput and replace startAmount +contract V3DutchOrderReactor is BaseReactor { + using Permit2Lib for ResolvedOrder; + using V3DutchOrderLib for V3DutchOrder; + using NonlinearDutchDecayLib for V3DutchOutput[]; + using NonlinearDutchDecayLib for V3DutchInput; + using ExclusivityLib for ResolvedOrder; + using FixedPointMathLib for uint256; + using MathExt for uint256; + + /// @notice thrown when an order's deadline is passed + error DeadlineReached(); + + /// @notice thrown when an order's cosignature does not match the expected cosigner + error InvalidCosignature(); + + /// @notice thrown when an order's cosigner input is greater than the specified + error InvalidCosignerInput(); + + /// @notice thrown when an order's cosigner output is less than the specified + error InvalidCosignerOutput(); + + constructor(IPermit2 _permit2, address _protocolFeeOwner) BaseReactor(_permit2, _protocolFeeOwner) {} + + /// @inheritdoc BaseReactor + function _resolve(SignedOrder calldata signedOrder) + internal + view + virtual + override + returns (ResolvedOrder memory resolvedOrder) + { + V3DutchOrder memory order = abi.decode(signedOrder.order, (V3DutchOrder)); + // hash the order _before_ overriding amounts, as this is the hash the user would have signed + bytes32 orderHash = order.hash(); + + _validateOrder(orderHash, order); + _updateWithCosignerAmounts(order); + _updateWithGasAdjustment(order); + + resolvedOrder = ResolvedOrder({ + info: order.info, + input: order.baseInput.decay(order.cosignerData.decayStartBlock), + outputs: order.baseOutputs.decay(order.cosignerData.decayStartBlock), + sig: signedOrder.sig, + hash: orderHash + }); + resolvedOrder.handleExclusiveOverrideBlock( + order.cosignerData.exclusiveFiller, + order.cosignerData.decayStartBlock, + order.cosignerData.exclusivityOverrideBps + ); + } + + /// @inheritdoc BaseReactor + function _transferInputTokens(ResolvedOrder memory order, address to) internal override { + permit2.permitWitnessTransferFrom( + order.toPermit(), + order.transferDetails(to), + order.info.swapper, + order.hash, + V3DutchOrderLib.PERMIT2_ORDER_TYPE, + order.sig + ); + } + + function _updateWithCosignerAmounts(V3DutchOrder memory order) internal pure { + if (order.cosignerData.inputAmount != 0) { + if (order.cosignerData.inputAmount > order.baseInput.startAmount) { + revert InvalidCosignerInput(); + } + order.baseInput.startAmount = order.cosignerData.inputAmount; + } + + if (order.cosignerData.outputAmounts.length != order.baseOutputs.length) { + revert InvalidCosignerOutput(); + } + uint256 outputsLength = order.baseOutputs.length; + for (uint256 i = 0; i < outputsLength; i++) { + V3DutchOutput memory output = order.baseOutputs[i]; + uint256 outputAmount = order.cosignerData.outputAmounts[i]; + if (outputAmount != 0) { + if (outputAmount < output.startAmount) { + revert InvalidCosignerOutput(); + } + output.startAmount = outputAmount; + } + } + } + + function _updateWithGasAdjustment(V3DutchOrder memory order) internal view { + // positive means an increase in gas + int256 gasDeltaGwei = block.basefee.sub(order.startingBaseFee); + + // Gas increase should increase input + int256 inputDelta = int256(order.baseInput.adjustmentPerGweiBaseFee) * gasDeltaGwei / 1 gwei; + order.baseInput.startAmount = order.baseInput.startAmount.boundedAdd(inputDelta, 0, order.baseInput.maxAmount); + + // Gas increase should decrease output + uint256 outputsLength = order.baseOutputs.length; + for (uint256 i = 0; i < outputsLength; i++) { + V3DutchOutput memory output = order.baseOutputs[i]; + int256 outputDelta = int256(output.adjustmentPerGweiBaseFee) * gasDeltaGwei / 1 gwei; + output.startAmount = output.startAmount.boundedSub(outputDelta, output.minAmount, type(uint256).max); + } + } + + /// @notice validate the dutch order fields + /// - deadline must have not passed + /// - cosigner is valid if specified + /// @dev Throws if the order is invalid + function _validateOrder(bytes32 orderHash, V3DutchOrder memory order) internal view { + if (order.info.deadline < block.timestamp) { + revert DeadlineReached(); + } + + CosignerLib.verify(order.cosigner, order.cosignerDigest(orderHash), order.cosignature); + } +} diff --git a/src/types/Uint16Array.sol b/src/types/Uint16Array.sol new file mode 100644 index 00000000..a940d33a --- /dev/null +++ b/src/types/Uint16Array.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @dev An uint16 array of max 16 values packed into a single uint256 +type Uint16Array is uint256; + +error IndexOutOfBounds(); +error InvalidArrLength(); + +function toUint256(uint16[] memory inputArray) pure returns (uint256 uint16Array) { + return Uint16Array.unwrap(toUint16Array(inputArray)); +} + +function fromUnderlying(uint256 value) pure returns (Uint16Array) { + return Uint16Array.wrap(value); +} + +// Helper for creating a packed uint256 from a uint16 array +function toUint16Array(uint16[] memory inputArray) pure returns (Uint16Array uint16Array) { + if (inputArray.length > 16) { + revert InvalidArrLength(); + } + uint256 packedData = 0; + + for (uint256 i = 0; i < inputArray.length; i++) { + packedData |= uint256(inputArray[i]) << (i * 16); + } + + uint16Array = Uint16Array.wrap(packedData); +} + +library Uint16ArrayLibrary { + // Retrieve the nth uint16 value from a packed uint256 + function getElement(Uint16Array packedData, uint256 n) public pure returns (uint16) { + if (n >= 16) { + revert IndexOutOfBounds(); + } + unchecked { + uint256 shiftAmount = n * 16; + uint16 result = uint16((Uint16Array.unwrap(packedData) >> shiftAmount) & 0xFFFF); + return result; + } + } +} diff --git a/test/lib/DutchDecayLib.t.sol b/test/lib/DutchDecayLib.t.sol index e635ffee..0123c9ef 100644 --- a/test/lib/DutchDecayLib.t.sol +++ b/test/lib/DutchDecayLib.t.sol @@ -74,27 +74,26 @@ contract DutchDecayLibTest is Test { assertEq(DutchDecayLib.decay(2 ether, 1 ether, 100, 200), 1 ether); } - function testDutchDecayBounded(uint256 startAmount, uint256 endAmount, uint256 decayStartTime, uint256 decayEndTime) + function testDutchDecayBounded(int256 startAmount, int256 endAmount, uint256 decayStartTime, uint256 decayEndTime) public { + vm.assume(startAmount >= 0); vm.assume(endAmount > startAmount); vm.assume(decayEndTime > decayStartTime); - uint256 decayed = DutchDecayLib.decay(startAmount, endAmount, decayStartTime, decayEndTime); - assertGe(decayed, startAmount); - assertLe(decayed, endAmount); + uint256 decayed = DutchDecayLib.decay(uint256(startAmount), uint256(endAmount), decayStartTime, decayEndTime); + assertGe(decayed, uint256(startAmount)); + assertLe(decayed, uint256(endAmount)); } - function testDutchDecayNegative( - uint256 startAmount, - uint256 endAmount, - uint256 decayStartTime, - uint256 decayEndTime - ) public { + function testDutchDecayNegative(int256 startAmount, int256 endAmount, uint256 decayStartTime, uint256 decayEndTime) + public + { + vm.assume(endAmount >= 0); vm.assume(endAmount < startAmount); vm.assume(decayEndTime > decayStartTime); - uint256 decayed = DutchDecayLib.decay(startAmount, endAmount, decayStartTime, decayEndTime); - assertLe(decayed, startAmount); - assertGe(decayed, endAmount); + uint256 decayed = DutchDecayLib.decay(uint256(startAmount), uint256(endAmount), decayStartTime, decayEndTime); + assertLe(decayed, uint256(startAmount)); + assertGe(decayed, uint256(endAmount)); } function testDutchDecayInvalidTimes( @@ -108,4 +107,17 @@ contract DutchDecayLibTest is Test { vm.expectRevert(DutchDecayLib.EndTimeBeforeStartTime.selector); DutchDecayLib.decay(startAmount, endAmount, decayStartTime, decayEndTime); } + + function testDutchDecayOverflow() public { + vm.expectRevert(); + DutchDecayLib.linearDecay(0, 100, 99, type(int256).max, -1); + + vm.expectRevert(); + DutchDecayLib.linearDecay(0, 100, 99, -1, type(int256).max); + } + + function testDutchDecayDivByZero() public { + vm.expectRevert(); + DutchDecayLib.linearDecay(100, 100, 99, 1, -1); + } } diff --git a/test/lib/ExclusivityLib.t.sol b/test/lib/ExclusivityLib.t.sol index eae4ac02..c6cc0e18 100644 --- a/test/lib/ExclusivityLib.t.sol +++ b/test/lib/ExclusivityLib.t.sol @@ -25,61 +25,84 @@ contract ExclusivityLibTest is Test { recipient = makeAddr("recipient"); } - function testExclusivity(address exclusive) public { + function testTimestampExclusivity(address exclusive) public { vm.assume(exclusive != address(0)); vm.prank(exclusive); - assertEq(exclusivity.hasFillingRights(exclusive, block.timestamp + 1), true); + assertEq(exclusivity.hasFillingRights(exclusive, block.timestamp + 1, block.timestamp), true); } - function testExclusivityFail(address caller, address exclusive, uint256 nowTime, uint256 exclusiveTimestamp) - public - { - vm.assume(nowTime <= exclusiveTimestamp); + function testExclusivityFail(address caller, address exclusive, uint256 nowTime, uint256 exclusiveEnd) public { + vm.assume(nowTime <= exclusiveEnd); vm.assume(caller != exclusive); vm.assume(exclusive != address(0)); - vm.warp(nowTime); vm.prank(caller); - assertEq(exclusivity.hasFillingRights(exclusive, exclusiveTimestamp), false); + assertEq(exclusivity.hasFillingRights(exclusive, exclusiveEnd, nowTime), false); } - function testNoExclusivity(address caller, uint256 nowTime, uint256 exclusiveTimestamp) public { - vm.warp(nowTime); + function testNoExclusivity(address caller, uint256 nowTime, uint256 exclusiveEnd) public { vm.prank(caller); - assertEq(exclusivity.hasFillingRights(address(0), exclusiveTimestamp), true); + assertEq(exclusivity.hasFillingRights(address(0), exclusiveEnd, nowTime), true); } - function testExclusivityPeriodOver(address caller, uint256 nowTime, uint256 exclusiveTimestamp) public { - vm.assume(nowTime > exclusiveTimestamp); - vm.warp(nowTime); + function testExclusivityPeriodOver(address caller, uint256 nowTime, uint256 exclusiveEnd) public { + vm.assume(nowTime > exclusiveEnd); vm.prank(caller); - assertEq(exclusivity.hasFillingRights(address(1), exclusiveTimestamp), true); + assertEq(exclusivity.hasFillingRights(address(1), exclusiveEnd, nowTime), true); } - function testHandleExclusiveOverridePass(address exclusive, uint256 overrideAmt, uint128 amount) public { + function testHandleExclusiveOverrideTimestampPass(address exclusive, uint256 overrideAmt, uint128 amount) public { vm.assume(overrideAmt < 10000); ResolvedOrder memory order; order.outputs = OutputsBuilder.single(token1, amount, recipient); vm.prank(exclusive); ResolvedOrder memory handled = - exclusivity.handleExclusiveOverride(order, exclusive, block.timestamp + 1, overrideAmt); + exclusivity.handleExclusiveOverrideTimestamp(order, exclusive, block.timestamp + 1, overrideAmt); + // no changes + assertEq(handled.outputs[0].amount, amount); + assertEq(handled.outputs[0].recipient, recipient); + } + + function testHandleExclusiveOverrideBlockPass(address exclusive, uint256 overrideAmt, uint128 amount) public { + vm.assume(overrideAmt < 10000); + ResolvedOrder memory order; + order.outputs = OutputsBuilder.single(token1, amount, recipient); + vm.prank(exclusive); + ResolvedOrder memory handled = + exclusivity.handleExclusiveOverrideBlock(order, exclusive, block.number + 1, overrideAmt); + // no changes + assertEq(handled.outputs[0].amount, amount); + assertEq(handled.outputs[0].recipient, recipient); + } + + function testHandleExclusiveOverrideTimestampPassNoExclusivity(address caller, uint256 overrideAmt, uint128 amount) + public + { + vm.assume(overrideAmt < 10000); + ResolvedOrder memory order; + order.outputs = OutputsBuilder.single(token1, amount, recipient); + vm.prank(caller); + ResolvedOrder memory handled = + exclusivity.handleExclusiveOverrideTimestamp(order, address(0), block.timestamp + 1, overrideAmt); // no changes assertEq(handled.outputs[0].amount, amount); assertEq(handled.outputs[0].recipient, recipient); } - function testHandleExclusiveOverridePassNoExclusivity(address caller, uint256 overrideAmt, uint128 amount) public { + function testHandleExclusiveOverrideBlockPassNoExclusivity(address caller, uint256 overrideAmt, uint128 amount) + public + { vm.assume(overrideAmt < 10000); ResolvedOrder memory order; order.outputs = OutputsBuilder.single(token1, amount, recipient); vm.prank(caller); ResolvedOrder memory handled = - exclusivity.handleExclusiveOverride(order, address(0), block.timestamp + 1, overrideAmt); + exclusivity.handleExclusiveOverrideBlock(order, address(0), block.number + 1, overrideAmt); // no changes assertEq(handled.outputs[0].amount, amount); assertEq(handled.outputs[0].recipient, recipient); } - function testHandleExclusiveOverridePassWindowPassed( + function testHandleExclusiveOverrideTimetampPassWindowPassed( address caller, address exclusive, uint256 overrideAmt, @@ -92,49 +115,105 @@ contract ExclusivityLibTest is Test { order.outputs = OutputsBuilder.single(token1, amount, recipient); vm.warp(100); vm.prank(caller); - ResolvedOrder memory handled = exclusivity.handleExclusiveOverride(order, address(0), 99, overrideAmt); + ResolvedOrder memory handled = exclusivity.handleExclusiveOverrideTimestamp(order, address(0), 99, overrideAmt); // no changes assertEq(handled.outputs[0].amount, amount); assertEq(handled.outputs[0].recipient, recipient); } - function testHandleExclusiveOverrideStrict(address caller, address exclusive, uint128 amount) public { + function testHandleExclusiveOverrideBlockPassWindowPassed( + address caller, + address exclusive, + uint256 overrideAmt, + uint128 amount + ) public { + vm.assume(overrideAmt < 10000); + vm.assume(exclusive != address(0)); + vm.assume(caller != exclusive); + ResolvedOrder memory order; + order.outputs = OutputsBuilder.single(token1, amount, recipient); + vm.roll(100); + vm.prank(caller); + ResolvedOrder memory handled = exclusivity.handleExclusiveOverrideBlock(order, address(0), 99, overrideAmt); + // no changes + assertEq(handled.outputs[0].amount, amount); + assertEq(handled.outputs[0].recipient, recipient); + } + + function testHandleExclusiveOverrideTimestampStrict(address caller, address exclusive, uint128 amount) public { vm.assume(caller != exclusive); vm.assume(exclusive != address(0)); ResolvedOrder memory order; order.outputs = OutputsBuilder.single(token1, amount, recipient); vm.prank(caller); vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); - exclusivity.handleExclusiveOverride(order, exclusive, block.timestamp + 1, 0); + exclusivity.handleExclusiveOverrideTimestamp(order, exclusive, block.timestamp + 1, 0); + } + + function testHandleExclusiveOverrideBlockStrict(address caller, address exclusive, uint128 amount) public { + vm.assume(caller != exclusive); + vm.assume(exclusive != address(0)); + ResolvedOrder memory order; + order.outputs = OutputsBuilder.single(token1, amount, recipient); + vm.prank(caller); + vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); + exclusivity.handleExclusiveOverrideBlock(order, exclusive, block.number + 1, 0); + } + + function testHandleExclusiveOverrideTimestamp() public { + ResolvedOrder memory order; + order.outputs = OutputsBuilder.single(token1, 1 ether, recipient); + uint256 overrideAmt = 3000; + vm.prank(address(2)); + ResolvedOrder memory handled = + exclusivity.handleExclusiveOverrideTimestamp(order, address(1), block.timestamp + 1, overrideAmt); + // assert overrideAmt applied + assertEq(handled.outputs[0].amount, 1.3 ether); + assertEq(handled.outputs[0].recipient, recipient); } - function testHandleExclusiveOverride() public { + function testHandleExclusiveOverrideBlock() public { ResolvedOrder memory order; order.outputs = OutputsBuilder.single(token1, 1 ether, recipient); uint256 overrideAmt = 3000; vm.prank(address(2)); ResolvedOrder memory handled = - exclusivity.handleExclusiveOverride(order, address(1), block.timestamp + 1, overrideAmt); + exclusivity.handleExclusiveOverrideBlock(order, address(1), block.number + 1, overrideAmt); // assert overrideAmt applied assertEq(handled.outputs[0].amount, 1.3 ether); assertEq(handled.outputs[0].recipient, recipient); } - function testHandleExclusiveOverrideRoundUp() public { + function testHandleExclusiveOverrideTimetampRoundUp() public { ResolvedOrder memory order; order.outputs = OutputsBuilder.single(token1, 1 ether + 1, recipient); uint256 overrideAmt = 3000; vm.prank(address(2)); ResolvedOrder memory handled = - exclusivity.handleExclusiveOverride(order, address(1), block.timestamp + 1, overrideAmt); + exclusivity.handleExclusiveOverrideTimestamp(order, address(1), block.timestamp + 1, overrideAmt); // assert overrideAmt applied assertEq(handled.outputs[0].amount, 1.3 ether + 2); assertEq(handled.outputs[0].recipient, recipient); } - function testHandleExclusiveOverrideApplied(address caller, address exclusive, uint256 overrideAmt, uint128 amount) - public - { + function testHandleExclusiveOverrideBlockRoundUp() public { + ResolvedOrder memory order; + order.outputs = OutputsBuilder.single(token1, 1 ether + 1, recipient); + uint256 overrideAmt = 3000; + vm.prank(address(2)); + ResolvedOrder memory handled = + exclusivity.handleExclusiveOverrideBlock(order, address(1), block.number + 1, overrideAmt); + // assert overrideAmt applied + assertEq(handled.outputs[0].amount, 1.3 ether + 2); + assertEq(handled.outputs[0].recipient, recipient); + } + + function testHandleExclusiveOverrideTimestampApplied( + address caller, + address exclusive, + uint256 overrideAmt, + uint128 amount + ) public { vm.assume(caller != exclusive); vm.assume(exclusive != address(0)); vm.assume(overrideAmt < 10000 && overrideAmt > 0); @@ -142,13 +221,58 @@ contract ExclusivityLibTest is Test { order.outputs = OutputsBuilder.single(token1, amount, recipient); vm.prank(caller); ResolvedOrder memory handled = - exclusivity.handleExclusiveOverride(order, exclusive, block.timestamp + 1, overrideAmt); + exclusivity.handleExclusiveOverrideTimestamp(order, exclusive, block.timestamp + 1, overrideAmt); // assert overrideAmt applied assertEq(handled.outputs[0].amount, uint256(amount).mulDivUp(10000 + overrideAmt, 10000)); assertEq(handled.outputs[0].recipient, recipient); } - function testHandleExclusiveOverrideAppliedMultiOutput( + function testHandleExclusiveOverrideBlockApplied( + address caller, + address exclusive, + uint256 overrideAmt, + uint128 amount + ) public { + vm.assume(caller != exclusive); + vm.assume(exclusive != address(0)); + vm.assume(overrideAmt < 10000 && overrideAmt > 0); + ResolvedOrder memory order; + order.outputs = OutputsBuilder.single(token1, amount, recipient); + vm.prank(caller); + ResolvedOrder memory handled = + exclusivity.handleExclusiveOverrideBlock(order, exclusive, block.number + 1, overrideAmt); + // assert overrideAmt applied + assertEq(handled.outputs[0].amount, uint256(amount).mulDivUp(10000 + overrideAmt, 10000)); + assertEq(handled.outputs[0].recipient, recipient); + } + + function testHandleExclusiveOverrideTimestampAppliedMultiOutput( + address caller, + address exclusive, + uint256 overrideAmt, + uint128[] memory fuzzAmounts + ) public { + vm.assume(caller != exclusive); + vm.assume(exclusive != address(0)); + vm.assume(overrideAmt < 10000 && overrideAmt > 0); + uint256[] memory amounts = new uint256[](fuzzAmounts.length); + for (uint256 i = 0; i < fuzzAmounts.length; i++) { + amounts[i] = fuzzAmounts[i]; + } + + ResolvedOrder memory order; + order.outputs = OutputsBuilder.multiple(token1, amounts, recipient); + vm.prank(caller); + ResolvedOrder memory handled = + exclusivity.handleExclusiveOverrideTimestamp(order, exclusive, block.timestamp + 1, overrideAmt); + // assert overrideAmt applied + for (uint256 i = 0; i < amounts.length; i++) { + assertEq(handled.outputs[i].amount, uint256(amounts[i]).mulDivUp(10000 + overrideAmt, 10000)); + assertEq(handled.outputs[i].recipient, recipient); + } + } + + function testHandleExclusiveOverrideBlockAppliedMultiOutput( address caller, address exclusive, uint256 overrideAmt, @@ -166,7 +290,7 @@ contract ExclusivityLibTest is Test { order.outputs = OutputsBuilder.multiple(token1, amounts, recipient); vm.prank(caller); ResolvedOrder memory handled = - exclusivity.handleExclusiveOverride(order, exclusive, block.timestamp + 1, overrideAmt); + exclusivity.handleExclusiveOverrideBlock(order, exclusive, block.number + 1, overrideAmt); // assert overrideAmt applied for (uint256 i = 0; i < amounts.length; i++) { assertEq(handled.outputs[i].amount, uint256(amounts[i]).mulDivUp(10000 + overrideAmt, 10000)); diff --git a/test/lib/MathExt.t.sol b/test/lib/MathExt.t.sol new file mode 100644 index 00000000..b23d48de --- /dev/null +++ b/test/lib/MathExt.t.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Test, console} from "forge-std/Test.sol"; +import {NonlinearDutchDecayLib} from "../../src/lib/NonlinearDutchDecayLib.sol"; +import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../../src/lib/V3DutchOrderLib.sol"; +import {MathExt} from "../../src/lib/MathExt.sol"; +import {ArrayBuilder} from "../util/ArrayBuilder.sol"; + +contract MathExtTest is Test { + using MathExt for uint256; + + /* sub(uint256 a, int256 b) tests */ + + function testSubIntFromUint() public { + assertEq(uint256(2).sub(int256(2)), 0); + assertEq(uint256(2).sub(int256(1)), 1); + assertEq(uint256(2).sub(int256(-1)), 3); + } + + function testSubNegIntFromUintRange(uint256 a, uint256 b) public { + vm.assume(a < 2 ** 255 - 1); + vm.assume(b <= UINT256_MAX - a); + assertEq(b.sub(0 - int256(a)), b + a); + } + + function testSubIntFromUintRange(uint256 a, uint256 b) public { + vm.assume(a >= 0); + vm.assume(b >= a); + vm.assume(a < 2 ** 255 - 1); + assertEq(b.sub(int256(a)), b - a); + } + + function testSubIntFromUintNegativeUint() public { + vm.expectRevert(); + uint256(1).sub(int256(2)); + } + + function testSubIntFromUintOverflow() public { + vm.expectRevert(); + UINT256_MAX.sub(-1); + } + + /* boundedSub(uint256 a, int256 b, uint256 min, uint256 max) tests */ + + function testBoundedSub(uint128 a, int128 b, uint256 max, uint256 min) public { + vm.assume(max >= min); + uint256 c = uint256(a).boundedSub(b, max, min); + assertGe(c, min); + assertLe(c, max); + } + + function testBoundedSubNegIntFromUintRange(uint256 a, uint256 b) public { + vm.assume(a < 2 ** 255 - 1); + vm.assume(b <= UINT256_MAX - a); + assertEq(b.boundedSub(0 - int256(a), 0, type(uint256).max), b + a); + } + + function testBoundedSubIntFromUintRange(uint256 a, uint256 b) public { + vm.assume(a >= 0); + vm.assume(b >= a); + vm.assume(a < 2 ** 255 - 1); + assertEq(b.boundedSub(int256(a), 0, type(uint256).max), b - a); + } + + function testBoundedSubIntFromUintNegativeUint() public { + assertEq(uint256(1).boundedSub(int256(2), 0, type(uint256).max), 0); + } + + function testBoundedSubIntFromUintOverflow() public { + assertEq(UINT256_MAX.boundedSub(-1, 0, type(uint256).max), type(uint256).max); + } + + /* sub(uint256 a, uint256 b) tests */ + + function testSubUintFromUint() public { + assertEq(uint256(2).sub(uint256(2)), 0); + assertEq(uint256(2).sub(uint256(1)), 1); + assertEq(uint256(2).sub(uint256(3)), -1); + } + + function testSubUintFromUintRange(uint256 a, uint256 b) public { + vm.assume(a >= b); + vm.assume(a < 2 ** 255 - 1); + assertEq(a.sub(b), int256(a - b)); + } + + function testSubUintFromUintNegativeUint(uint256 a, uint256 b) public { + vm.assume(b >= a); + vm.assume(b < 2 ** 255 - 1); + int256 c = a.sub(b); + assertEq(c, int256(a) - int256(b)); + } + + function testSubUintFromUintUnderflow() public { + vm.expectRevert(); + uint256(0).sub(type(uint256).max); + } + + function testSubUintFromUintOverflow() public { + vm.expectRevert(); + UINT256_MAX.sub(uint256(1)); + } + + /* bound(uint256 value, uint256 min, uint256 max) */ + + function testBound(uint256 value, uint256 min, uint256 max) public { + vm.assume(min <= max); + uint256 result = value.bound(min, max); + assertLe(result, max); + assertGe(result, min); + } + + function testBoundValueInBounds(uint256 value, uint256 min, uint256 max) public { + vm.assume(min <= value); + vm.assume(value <= max); + uint256 result = value.bound(min, max); + assertEq(result, value); + } +} diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol new file mode 100644 index 00000000..c4217418 --- /dev/null +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {console} from "forge-std/console.sol"; +import {DutchDecayLib} from "../../src/lib/DutchDecayLib.sol"; +import {NonlinearDutchDecayLib} from "../../src/lib/NonlinearDutchDecayLib.sol"; +import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../../src/lib/V3DutchOrderLib.sol"; +import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol"; +import {ArrayBuilder} from "../util/ArrayBuilder.sol"; +import {CurveBuilder} from "../util/CurveBuilder.sol"; + +/// @notice mock contract to test NonlinearDutchDecayLib functionality +contract MockNonlinearDutchDecayLibContract { + function decay( + NonlinearDutchDecay memory curve, + uint256 startAmount, + uint256 decayStartBlock, + uint256 minAmount, + uint256 maxAmount + ) public view { + NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, minAmount, maxAmount); + } +} + +contract NonlinearDutchDecayLibTest is Test, GasSnapshot { + MockNonlinearDutchDecayLibContract mockNonlinearDutchDecayLibContract = new MockNonlinearDutchDecayLibContract(); + + function testLocateCurvePositionSingle() public { + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(1, 0); + + snapStart("V3-LocateCurvePositionSingle"); + (uint16 startPoint, uint16 endPoint, int256 relStartAmount, int256 relEndAmount) = + NonlinearDutchDecayLib.locateCurvePosition(curve, 1); + assertEq(startPoint, 0); + assertEq(endPoint, 1); + assertEq(relStartAmount, 0); + assertEq(relEndAmount, 0); + + (startPoint, endPoint, relStartAmount, relEndAmount) = NonlinearDutchDecayLib.locateCurvePosition(curve, 2); + assertEq(startPoint, 1); + assertEq(endPoint, 1); + assertEq(relStartAmount, 0); + assertEq(relEndAmount, 0); + snapEnd(); + } + + function testLocateCurvePositionMulti() public { + uint16[] memory blocks = new uint16[](3); + blocks[0] = 100; + blocks[1] = 200; + blocks[2] = 300; + int256[] memory decayAmounts = new int256[](3); + decayAmounts[0] = -1 ether; // 2 ether + decayAmounts[1] = 0 ether; // 1 ether + decayAmounts[2] = 1 ether; // 0 ether + NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); + + snapStart("V3-LocateCurvePositionMulti"); + // currentRelativeBlock shouldn't be less than the first block + // but testing behavior anyways + (uint16 startPoint, uint16 endPoint, int256 relStartAmount, int256 relEndAmount) = + NonlinearDutchDecayLib.locateCurvePosition(curve, 50); + assertEq(startPoint, 0); + assertEq(endPoint, 100); + assertEq(relStartAmount, 0); + assertEq(relEndAmount, -1 ether); + + (startPoint, endPoint, relStartAmount, relEndAmount) = NonlinearDutchDecayLib.locateCurvePosition(curve, 100); + assertEq(startPoint, 0); + assertEq(endPoint, 100); + assertEq(relStartAmount, 0); + assertEq(relEndAmount, -1 ether); + + (startPoint, endPoint, relStartAmount, relEndAmount) = NonlinearDutchDecayLib.locateCurvePosition(curve, 150); + assertEq(startPoint, 100); + assertEq(endPoint, 200); + assertEq(relStartAmount, -1 ether); + assertEq(relEndAmount, 0 ether); + + (startPoint, endPoint, relStartAmount, relEndAmount) = NonlinearDutchDecayLib.locateCurvePosition(curve, 200); + assertEq(startPoint, 100); + assertEq(endPoint, 200); + assertEq(relStartAmount, -1 ether); + assertEq(relEndAmount, 0 ether); + + (startPoint, endPoint, relStartAmount, relEndAmount) = NonlinearDutchDecayLib.locateCurvePosition(curve, 250); + assertEq(startPoint, 200); + assertEq(endPoint, 300); + assertEq(relStartAmount, 0 ether); + assertEq(relEndAmount, 1 ether); + + (startPoint, endPoint, relStartAmount, relEndAmount) = NonlinearDutchDecayLib.locateCurvePosition(curve, 300); + assertEq(startPoint, 200); + assertEq(endPoint, 300); + assertEq(relStartAmount, 0 ether); + assertEq(relEndAmount, 1 ether); + + (startPoint, endPoint, relStartAmount, relEndAmount) = NonlinearDutchDecayLib.locateCurvePosition(curve, 350); + assertEq(startPoint, 300); + assertEq(endPoint, 300); + assertEq(relStartAmount, 1 ether); + assertEq(relEndAmount, 1 ether); + snapEnd(); + } + + function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { + // Empty curve + snapStart("V3-DutchDecayNoDecay"); + assertEq( + NonlinearDutchDecayLib.decay( + CurveBuilder.emptyCurve(), startAmount, decayStartBlock, startAmount, startAmount + ), + startAmount + ); + + // Single value with 0 amount change + assertEq( + NonlinearDutchDecayLib.decay( + CurveBuilder.singlePointCurve(1, 0), startAmount, decayStartBlock, startAmount, startAmount + ), + startAmount + ); + snapEnd(); + } + + function testDutchDecayNoDecayYet() public { + uint256 decayStartBlock = 200; + uint256 startAmount = 1 ether; + int256 decayAmount = -1 ether; + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(1, decayAmount); + snapStart("V3-DutchDecayNoDecayYet"); + vm.roll(100); + // at decayStartBlock + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, startAmount, 2 ether), startAmount); + + vm.roll(80); + // before decayStartBlock + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, startAmount, 2 ether), startAmount); + snapEnd(); + } + + function testDutchDecayNoDecayYetNegative() public { + uint256 decayStartBlock = 200; + uint256 startAmount = 1 ether; + int256 decayAmount = 1 ether; + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(1, decayAmount); + snapStart("V3-DutchDecayNoDecayYetNegative"); + vm.roll(100); + // at decayStartBlock + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0, 1 ether), startAmount); + + vm.roll(80); + // before decayStartBlock + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0, 1 ether), startAmount); + snapEnd(); + } + + function testDutchDecay() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + int256 decayAmount = -1 ether; + 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); + + vm.roll(180); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.8 ether); + + vm.roll(110); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.1 ether); + + vm.roll(190); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.9 ether); + snapEnd(); + } + + function testDutchDecayNegative() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 2 ether; + int256 decayAmount = 1 ether; + 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); + + vm.roll(180); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.2 ether); + + vm.roll(110); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.9 ether); + + vm.roll(190); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.1 ether); + snapEnd(); + } + + function testDutchDecayFullyDecayed() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + int256 decayAmount = -1 ether; + 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); + + vm.warp(250); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 2 ether); + snapEnd(); + } + + function testDutchDecayFullyDecayedNegative() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 2 ether; + int256 decayAmount = 1 ether; + 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); + + vm.warp(250); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1 ether); + snapEnd(); + } + + function testDutchDecayRange(uint256 startAmount, int256 decayAmount, uint256 decayStartBlock, uint16 decayDuration) + public + { + vm.assume(decayAmount > 0); + vm.assume(startAmount <= uint256(type(int256).max - decayAmount)); + + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(decayDuration, 0 - int256(decayAmount)); + snapStart("V3-DutchDecayRange"); + uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0, type(uint256).max); + assertGe(decayed, startAmount); + assertLe(decayed, startAmount + uint256(decayAmount)); + snapEnd(); + } + + function testDutchDecayBounded( + uint256 startAmount, + int256 decayAmount, + uint256 decayStartBlock, + uint16 decayDuration, + uint256 minAmount, + uint256 maxAmount + ) 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); + assertGe(decayed, minAmount); + assertLe(decayed, maxAmount); + snapEnd(); + } + + function testDutchDecayNegative( + uint256 startAmount, + uint256 decayAmount, + uint256 decayStartBlock, + uint16 decayDuration + ) public { + vm.assume(decayAmount > 0); + vm.assume(decayAmount < 2 ** 255 - 1); + // can't have neg prices + vm.assume(startAmount >= decayAmount); + vm.assume(startAmount <= UINT256_MAX - decayAmount); + + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(decayDuration, int256(decayAmount)); + snapStart("V3-DutchDecayNegative"); + uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0, type(uint256).max); + assertLe(decayed, startAmount); + assertGe(decayed, startAmount - decayAmount); + snapEnd(); + } + + function testMultiPointDutchDecay() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + uint16[] memory blocks = new uint16[](3); + blocks[0] = 100; // block 200 + blocks[1] = 200; // block 300 + blocks[2] = 300; // block 400 + int256[] memory decayAmounts = new int256[](3); + decayAmounts[0] = -1 ether; // 2 ether + decayAmounts[1] = 0 ether; // 1 ether + decayAmounts[2] = 1 ether; // 0 ether + 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); + + vm.roll(150); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1.5 ether); + + vm.roll(200); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 2 ether); + + vm.roll(210); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1.9 ether); + + vm.roll(290); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1.1 ether); + + vm.roll(300); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1 ether); + + vm.roll(350); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 0.5 ether); + + vm.roll(400); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 0 ether); + + vm.roll(500); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 0 ether); + snapEnd(); + } + + function testExtendedMultiPointDutchDecay() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + uint16[] memory blocks = new uint16[](16); + blocks[0] = 100; // block 200 + blocks[1] = 200; // block 300 + blocks[2] = 300; // block 400 + blocks[3] = 400; // block 500 + blocks[4] = 500; // block 600 + blocks[5] = 600; // block 700 + blocks[6] = 700; // block 800 + blocks[7] = 800; // block 900 + blocks[8] = 900; // block 1000 + blocks[9] = 1000; // block 1100 + blocks[10] = 1100; // block 1200 + blocks[11] = 1200; // block 1300 + blocks[12] = 1300; // block 1400 + blocks[13] = 1400; // block 1500 + blocks[14] = 1500; // block 1600 + blocks[15] = 1600; // block 1700 + + int256[] memory decayAmounts = new int256[](16); + decayAmounts[0] = -0.1 ether; + decayAmounts[1] = -0.2 ether; + decayAmounts[2] = -0.3 ether; + decayAmounts[3] = -0.4 ether; + decayAmounts[4] = -0.5 ether; + decayAmounts[5] = -0.6 ether; + decayAmounts[6] = -0.7 ether; + decayAmounts[7] = -0.8 ether; + decayAmounts[8] = -0.9 ether; + decayAmounts[9] = -1 ether; + decayAmounts[10] = -0.9 ether; + decayAmounts[11] = -0.8 ether; + decayAmounts[12] = -0.7 ether; + decayAmounts[13] = -0.6 ether; + decayAmounts[14] = -0.5 ether; + decayAmounts[15] = -0.4 ether; + + NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); + snapStart("V3-ExtendedMultiPointDutchDecay"); + + vm.roll(50); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 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) + + vm.roll(200); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 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) + + vm.roll(300); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 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) + + vm.roll(400); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 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) + + vm.roll(500); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 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 + + vm.roll(700); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 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 + + vm.roll(900); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 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 + + vm.roll(1100); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 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 + + vm.roll(1300); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 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 + + vm.roll(1500); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 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 + + vm.roll(1650); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.45 ether); // 1 + 0.45 ether + + snapEnd(); + } + + /* Invalid order scenarios */ + + function testDutchDecayNonAscendingBlocks() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + uint16[] memory blocks = new uint16[](3); + blocks[0] = 200; // block 300 + blocks[1] = 100; // block 200 + blocks[2] = 300; // block 400 + int256[] memory decayAmounts = new int256[](3); + decayAmounts[0] = -1 ether; // 2 ether + decayAmounts[1] = 0 ether; // 1 ether + 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); + } + + function testDutchDecayToNegative() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + 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); + } + + function testDutchOverflowDecay() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + int256 decayAmount = type(int256).min; + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); + vm.roll(150); + vm.expectRevert(); + mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock, 0 ether, 1 ether); + } + + function testDutchMismatchedDecay() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + 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); + } +} diff --git a/test/lib/Uint16Array.t.sol b/test/lib/Uint16Array.t.sol new file mode 100644 index 00000000..e87ae21f --- /dev/null +++ b/test/lib/Uint16Array.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; +import {NonlinearDutchDecayLib} from "../../src/lib/NonlinearDutchDecayLib.sol"; +import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../../src/lib/V3DutchOrderLib.sol"; +import {ArrayBuilder} from "../util/ArrayBuilder.sol"; +import { + Uint16ArrayLibrary, + Uint16Array, + toUint16Array, + InvalidArrLength, + IndexOutOfBounds +} from "../../src/types/Uint16Array.sol"; + +contract Uint16ArrayTest is Test { + using Uint16ArrayLibrary for Uint16Array; + + function testGetElement(uint16 value, uint16 length) public { + vm.assume(length <= 16); + Uint16Array packedArr = toUint16Array(ArrayBuilder.fillUint16(length, value)); + for (uint256 i = 0; i < length; i++) { + assertEq(packedArr.getElement(i), value); + } + } + + function testToUint16ArrayRevert() public { + vm.expectRevert(InvalidArrLength.selector); + toUint16Array(ArrayBuilder.fillUint16(17, 1)); + } + + function testGetElementRevert() public { + Uint16Array packedArr = toUint16Array(ArrayBuilder.fillUint16(5, 1)); + vm.expectRevert(IndexOutOfBounds.selector); + packedArr.getElement(16); + } +} diff --git a/test/reactors/ExclusiveDutchOrderReactor.t.sol b/test/reactors/ExclusiveDutchOrderReactor.t.sol index daec3ff8..1c8c6418 100644 --- a/test/reactors/ExclusiveDutchOrderReactor.t.sol +++ b/test/reactors/ExclusiveDutchOrderReactor.t.sol @@ -444,6 +444,7 @@ contract ExclusiveDutchOrderReactorTest is PermitSignature, DeployPermit2, BaseD ) public { vm.assume(exclusive != address(0)); vm.assume(exclusive != caller); + vm.assume(exclusive != address(fillContract)); vm.assume(overrideAmt > 0 && overrideAmt < 10000); tokenIn.mint(address(swapper), amountIn); tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); diff --git a/test/reactors/V2DutchOrderReactor.t.sol b/test/reactors/V2DutchOrderReactor.t.sol index a94a5b51..a9df64a5 100644 --- a/test/reactors/V2DutchOrderReactor.t.sol +++ b/test/reactors/V2DutchOrderReactor.t.sol @@ -386,7 +386,7 @@ contract V2DutchOrderTest is PermitSignature, DeployPermit2, BaseDutchOrderReact assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); } - function testExclusiveOverrideInvalidCallerCosignedAmountOutput() public { + function testExclusiveAppliesExclusiveOverride() public { uint256 inputAmount = 1 ether; uint256 outputAmount = 1 ether; uint256 exclusivityOverrideBps = 10; diff --git a/test/reactors/V3DutchOrderReactor.t.sol b/test/reactors/V3DutchOrderReactor.t.sol new file mode 100644 index 00000000..55e63c22 --- /dev/null +++ b/test/reactors/V3DutchOrderReactor.t.sol @@ -0,0 +1,1466 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; +import {DeployPermit2} from "../util/DeployPermit2.sol"; +import { + V3DutchOrder, + V3DutchOrderLib, + CosignerData, + V3DutchOrderReactor, + ResolvedOrder, + BaseReactor +} from "../../src/reactors/V3DutchOrderReactor.sol"; +import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../../src/lib/V3DutchOrderLib.sol"; +import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol"; +import {OrderInfo, InputToken, SignedOrder, OutputToken} from "../../src/base/ReactorStructs.sol"; +import {ExclusivityLib} from "../../src/lib/ExclusivityLib.sol"; +import {DutchDecayLib} from "../../src/lib/DutchDecayLib.sol"; +import {CurrencyLibrary, NATIVE} from "../../src/lib/CurrencyLibrary.sol"; +import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; +import {ArrayBuilder} from "../util/ArrayBuilder.sol"; +import {MockERC20} from "../util/mock/MockERC20.sol"; +import {OutputsBuilder} from "../util/OutputsBuilder.sol"; +import {MockFillContract} from "../util/mock/MockFillContract.sol"; +import {MockFillContractWithOutputOverride} from "../util/mock/MockFillContractWithOutputOverride.sol"; +import {PermitSignature} from "../util/PermitSignature.sol"; +import {ReactorEvents} from "../../src/base/ReactorEvents.sol"; +import {BaseReactorTest} from "../base/BaseReactor.t.sol"; +import {CurveBuilder} from "../util/CurveBuilder.sol"; +import {OrderQuoter} from "../../src/lens/OrderQuoter.sol"; +import {Solarray} from "solarray/Solarray.sol"; +import {MathExt} from "../../src/lib/MathExt.sol"; + +contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { + using OrderInfoBuilder for OrderInfo; + using V3DutchOrderLib for V3DutchOrder; + + OrderQuoter quoter; + + using MathExt for uint256; + + constructor() { + quoter = new OrderQuoter(); + } + + uint256 constant cosignerPrivateKey = 0x99999999; + + function name() public pure override returns (string memory) { + return "V3DutchOrder"; + } + + function createReactor() public override returns (BaseReactor) { + return new V3DutchOrderReactor(permit2, PROTOCOL_FEE_OWNER); + } + + /// @dev Create and return a basic single Dutch limit order along with its signature, orderHash, and orderInfo + function createAndSignOrder(ResolvedOrder memory request) + public + view + override + returns (SignedOrder memory signedOrder, bytes32 orderHash) + { + V3DutchOutput[] memory outputs = new V3DutchOutput[](request.outputs.length); + for (uint256 i = 0; i < request.outputs.length; i++) { + OutputToken memory output = request.outputs[i]; + outputs[i] = V3DutchOutput({ + token: output.token, + startAmount: output.amount, + curve: CurveBuilder.emptyCurve(), + recipient: output.recipient, + minAmount: output.amount, + adjustmentPerGweiBaseFee: 0 + }); + } + + uint256[] memory outputAmounts = new uint256[](request.outputs.length); + for (uint256 i = 0; i < request.outputs.length; i++) { + outputAmounts[i] = 0; + } + + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(0), + exclusivityOverrideBps: 0, + inputAmount: 0, + outputAmounts: outputAmounts + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: request.info, + cosigner: vm.addr(cosignerPrivateKey), + baseInput: V3DutchInput( + request.input.token, request.input.amount, CurveBuilder.emptyCurve(), request.input.amount, 0 + ), + baseOutputs: outputs, + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + orderHash = order.hash(); + order.cosignature = cosignOrder(orderHash, cosignerData); + return (SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), orderHash); + } + + /* Cosigner tests */ + + function testV3InputOverrideWorse() public { + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(0), + exclusivityOverrideBps: 0, + // override is more input tokens than expected + inputAmount: 0.9 ether, + outputAmounts: ArrayBuilder.fill(1, 1.1 ether) + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), + cosigner: vm.addr(cosignerPrivateKey), + baseInput: V3DutchInput(tokenIn, 0.8 ether, CurveBuilder.emptyCurve(), 1 ether, 0), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1 ether, 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper + ), + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + order.cosignature = cosignOrder(order.hash(), cosignerData); + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + vm.expectRevert(V3DutchOrderReactor.InvalidCosignerInput.selector); + fillContract.execute(signedOrder); + } + + function testV3OutputOverrideWorse() public { + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(0), + exclusivityOverrideBps: 0, + // override is more input tokens than expected + inputAmount: 1 ether, + outputAmounts: ArrayBuilder.fill(1, 0.9 ether) + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), + cosigner: vm.addr(cosignerPrivateKey), + baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether, 0), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1 ether, 0.8 ether, CurveBuilder.singlePointCurve(1, 0.2 ether), swapper + ), + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + order.cosignature = cosignOrder(order.hash(), cosignerData); + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + vm.expectRevert(V3DutchOrderReactor.InvalidCosignerOutput.selector); + fillContract.execute(signedOrder); + } + + function testV3OutputOverrideWrongLength() public { + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(0), + exclusivityOverrideBps: 0, + // override is more input tokens than expected + inputAmount: 1 ether, + outputAmounts: ArrayBuilder.fill(2, 1.1 ether) + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), + cosigner: vm.addr(cosignerPrivateKey), + baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether, 0), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1 ether, 0.8 ether, CurveBuilder.singlePointCurve(1, 0.2 ether), swapper + ), + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + order.cosignature = cosignOrder(order.hash(), cosignerData); + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + vm.expectRevert(V3DutchOrderReactor.InvalidCosignerOutput.selector); + fillContract.execute(signedOrder); + } + + function testV3OverrideInput() public { + uint256 outputAmount = 1 ether; + uint256 overriddenInputAmount = 0.7 ether; + tokenIn.mint(swapper, overriddenInputAmount); + tokenOut.mint(address(fillContract), outputAmount); + tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(0), + exclusivityOverrideBps: 0, + inputAmount: overriddenInputAmount, + outputAmounts: ArrayBuilder.fill(1, 0) + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), + cosigner: vm.addr(cosignerPrivateKey), + baseInput: V3DutchInput(tokenIn, 0.8 ether, CurveBuilder.emptyCurve(), 1 ether, 0), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), outputAmount, outputAmount, CurveBuilder.singlePointCurve(1, 0 ether), swapper + ), + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + order.cosignature = cosignOrder(order.hash(), cosignerData); + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + + _snapStart("V3-InputOverride"); + fillContract.execute(signedOrder); + snapEnd(); + + assertEq(tokenIn.balanceOf(swapper), 0); + assertEq(tokenOut.balanceOf(swapper), outputAmount); + assertEq(tokenIn.balanceOf(address(fillContract)), overriddenInputAmount); + } + + function testV3OverrideOutput() public { + uint256 overriddenOutputAmount = 1.1 ether; + uint256 inputAmount = 1 ether; + tokenIn.mint(swapper, inputAmount); + tokenOut.mint(address(fillContract), overriddenOutputAmount); + tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(0), + exclusivityOverrideBps: 0, + inputAmount: 0, + outputAmounts: ArrayBuilder.fill(1, overriddenOutputAmount) + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), + cosigner: vm.addr(cosignerPrivateKey), + baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount, 0), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1 ether, 0.9 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper + ), + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + order.cosignature = cosignOrder(order.hash(), cosignerData); + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + + _snapStart("V3-OutputOverride"); + fillContract.execute(signedOrder); + snapEnd(); + + assertEq(tokenIn.balanceOf(swapper), 0); + assertEq(tokenOut.balanceOf(swapper), overriddenOutputAmount); + assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); + } + + function testV3StrictExclusivityInvalidCaller() public { + uint256 inputAmount = 1 ether; + tokenIn.mint(swapper, inputAmount); + tokenOut.mint(address(fillContract), inputAmount); + tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(1), + exclusivityOverrideBps: 0, + inputAmount: 0, + outputAmounts: ArrayBuilder.fill(1, 0) + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), + cosigner: vm.addr(cosignerPrivateKey), + baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount, 0), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1 ether, 0.9 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper + ), + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + order.cosignature = cosignOrder(order.hash(), cosignerData); + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); + fillContract.execute(signedOrder); + } + + function testV3StrictExclusivityValidCaller() public { + uint256 inputAmount = 1 ether; + uint256 outputAmount = 1 ether; + tokenIn.mint(swapper, inputAmount); + tokenOut.mint(address(fillContract), inputAmount); + tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(fillContract), + exclusivityOverrideBps: 0, + inputAmount: 0, + outputAmounts: ArrayBuilder.fill(1, 0) + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), + cosigner: vm.addr(cosignerPrivateKey), + baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount, 0), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1 ether, 0.9 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper + ), + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + order.cosignature = cosignOrder(order.hash(), cosignerData); + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + vm.prank(address(1)); + + _snapStart("V3-ExclusiveFiller"); + fillContract.execute(signedOrder); + snapEnd(); + + assertEq(tokenIn.balanceOf(swapper), 0); + assertEq(tokenOut.balanceOf(swapper), outputAmount); + assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); + } + + function testV3AppliesExclusiveOverride() public { + uint256 inputAmount = 1 ether; + uint256 outputAmount = 1 ether; + uint256 exclusivityOverrideBps = 10; + tokenIn.mint(swapper, inputAmount); + tokenOut.mint(address(fillContract), outputAmount * 2); + tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(1), + exclusivityOverrideBps: exclusivityOverrideBps, + inputAmount: 0, + outputAmounts: ArrayBuilder.fill(1, outputAmount) + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), + cosigner: vm.addr(cosignerPrivateKey), + baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount, 0), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1 ether, 0.9 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper + ), + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + order.cosignature = cosignOrder(order.hash(), cosignerData); + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + fillContract.execute(signedOrder); + assertEq(tokenIn.balanceOf(swapper), 0); + assertEq(tokenOut.balanceOf(swapper), outputAmount * (10000 + exclusivityOverrideBps) / 10000); + assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); + } + + function testV3ExclusiveOverrideInvalidCallerNoCosignedAmountOutput() public { + uint256 inputAmount = 1 ether; + uint256 outputAmount = 1 ether; + uint256 exclusivityOverrideBps = 10; + tokenIn.mint(swapper, inputAmount); + tokenOut.mint(address(fillContract), outputAmount * 2); + tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(1), + exclusivityOverrideBps: exclusivityOverrideBps, + inputAmount: 0, + outputAmounts: ArrayBuilder.fill(1, 0) + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), + cosigner: vm.addr(cosignerPrivateKey), + baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount, 0), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1 ether, 0.9 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper + ), + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + order.cosignature = cosignOrder(order.hash(), cosignerData); + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + fillContract.execute(signedOrder); + assertEq(tokenIn.balanceOf(swapper), 0); + // still overrides the base swapper signed amount + assertEq(tokenOut.balanceOf(swapper), outputAmount * (10000 + exclusivityOverrideBps) / 10000); + assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); + } + + /* Validation tests */ + + function testV3WrongCosigner() public { + address wrongCosigner = makeAddr("wrongCosigner"); + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(0), + exclusivityOverrideBps: 0, + inputAmount: 1 ether, + outputAmounts: ArrayBuilder.fill(1, 1 ether) + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), + cosigner: wrongCosigner, + baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether, 0), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1 ether, 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper + ), + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + order.cosignature = cosignOrder(order.hash(), cosignerData); + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + vm.expectRevert(V3DutchOrderReactor.InvalidCosignature.selector); + fillContract.execute(signedOrder); + } + + function testV3InvalidCosignature() public { + address wrongCosigner = makeAddr("wrongCosigner"); + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: block.number, + exclusiveFiller: address(0), + exclusivityOverrideBps: 0, + inputAmount: 1 ether, + outputAmounts: ArrayBuilder.fill(1, 1 ether) + }); + + V3DutchOrder memory order = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), + cosigner: wrongCosigner, + baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether, 0), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1 ether, 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper + ), + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + order.cosignature = bytes.concat(keccak256("invalidSignature"), keccak256("invalidSignature"), hex"33"); + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + vm.expectRevert(V3DutchOrderReactor.InvalidCosignature.selector); + fillContract.execute(signedOrder); + } + + function testV3ExecutePastDeadline() public { + uint256 inputAmount = 1 ether; + uint256 outputAmount = 1 ether; + uint256 startBlock = block.number; + uint256 deadline = block.timestamp + 1000; + tokenOut.mint(address(fillContract), uint256(outputAmount) * 100); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: block.number, + startBlock: startBlock, + deadline: deadline, + input: V3DutchInput( + tokenIn, + inputAmount, + CurveBuilder.singlePointCurve(1000, 0 - int256(inputAmount * 10 / 100)), + inputAmount * 110 / 100, + 0 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + outputAmount, + outputAmount * 90 / 100, + CurveBuilder.singlePointCurve(1000, int256(outputAmount * 10 / 100)), + address(swapper) + ) + }) + ); + vm.warp(deadline + 1); + vm.expectRevert(V3DutchOrderReactor.DeadlineReached.selector); + fillContract.execute(order); + } + + /* Block decay tests */ + + function testV3ExecuteInputAndOutputHalfDecay() public { + uint256 inputAmount = 1 ether; + uint256 outputAmount = 1 ether; + uint256 startBlock = block.number; + uint256 deadline = block.timestamp + 1000; + tokenOut.mint(address(fillContract), uint256(outputAmount) * 100); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: block.number, + startBlock: startBlock, + deadline: deadline, + input: V3DutchInput( + tokenIn, + inputAmount, + CurveBuilder.singlePointCurve(1000, 0 - int256(inputAmount * 10 / 100)), + inputAmount * 110 / 100, + 0 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + outputAmount, + outputAmount * 90 / 100, + CurveBuilder.singlePointCurve(1000, int256(inputAmount * 10 / 100)), + address(swapper) + ) + }) + ); + uint256 swapperInputBalanceStart = tokenIn.balanceOf(address(swapper)); + uint256 swapperOutputBalanceStart = tokenOut.balanceOf(address(swapper)); + vm.expectEmit(false, true, true, false, address(reactor)); + emit Fill(keccak256("not checked"), address(fillContract), swapper, 0); + vm.roll(startBlock + 500); + fillContract.execute(order); + uint256 swapperInputBalanceEnd = tokenIn.balanceOf(address(swapper)); + uint256 swapperOutputBalanceEnd = tokenOut.balanceOf(address(swapper)); + assertEq(swapperInputBalanceStart - swapperInputBalanceEnd, inputAmount * 105 / 100); + assertEq(swapperOutputBalanceEnd - swapperOutputBalanceStart, outputAmount * 95 / 100); + } + + function testV3ExecuteInputAndOutputFullDecay() public { + uint256 inputAmount = 1 ether; + uint256 outputAmount = 1 ether; + uint256 startBlock = block.number; + uint256 deadline = block.timestamp + 1000; + tokenOut.mint(address(fillContract), uint256(outputAmount) * 100); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: block.number, + startBlock: startBlock, + deadline: deadline, + input: V3DutchInput( + tokenIn, + inputAmount, + CurveBuilder.singlePointCurve(1000, 0 - int256(inputAmount * 10 / 100)), + inputAmount * 110 / 100, + 0 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + outputAmount, + outputAmount * 90 / 100, + CurveBuilder.singlePointCurve(1000, int256(outputAmount * 10 / 100)), + address(swapper) + ) + }) + ); + uint256 swapperInputBalanceStart = tokenIn.balanceOf(address(swapper)); + uint256 swapperOutputBalanceStart = tokenOut.balanceOf(address(swapper)); + vm.expectEmit(false, true, true, false, address(reactor)); + emit Fill(keccak256("not checked"), address(fillContract), swapper, 0); + vm.roll(startBlock + 1000); + fillContract.execute(order); + uint256 swapperInputBalanceEnd = tokenIn.balanceOf(address(swapper)); + uint256 swapperOutputBalanceEnd = tokenOut.balanceOf(address(swapper)); + assertEq(swapperInputBalanceStart - swapperInputBalanceEnd, inputAmount * 110 / 100); + assertEq(swapperOutputBalanceEnd - swapperOutputBalanceStart, outputAmount * 90 / 100); + } + + function testV3ResolveNotStarted() public { + uint256 currentBlock = 1000; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: currentBlock + 100, + deadline: currentBlock + 200, + input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(200, 1000), 2000, 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 0, 0, CurveBuilder.singlePointCurve(200, -100), address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 1000); + } + + function testV3ResolveOutputHalfwayDecayed() public { + uint256 currentBlock = 1000; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: currentBlock - 100, + deadline: currentBlock + 200, + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(100, 0), 0, 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 2000, 1000, CurveBuilder.singlePointCurve(200, 1000), address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1500); + assertEq(resolvedOrder.input.amount, 0); + } + + function testV3ResolveOutputFullyDecayed() public { + uint256 currentBlock = 1000; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: currentBlock - 200, + deadline: currentBlock + 200, + input: V3DutchInput(tokenIn, 100, CurveBuilder.singlePointCurve(200, 100), 100, 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 2000, 1000, CurveBuilder.singlePointCurve(200, 1000), address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 0); + } + + function testV3ResolveInputHalfwayDecayed() public { + uint256 currentBlock = 1000; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: currentBlock - 100, + deadline: currentBlock + 200, + input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(200, 1000), 1000, 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1000, 1000, CurveBuilder.singlePointCurve(200, 0), address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 500); + } + + function testV3ResolveInputFullyDecayed() public { + uint256 currentBlock = 1000; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: currentBlock - 100, + deadline: currentBlock + 200, + input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(100, 1000), 1000, 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 1000, 1000, CurveBuilder.singlePointCurve(100, 0), address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 0); + } + + // 1000 - (100 * (1659087340-1659029740) / (65535)) = 913 + function testV3ResolveEndBlockAfterNow() 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.outputs.length, 1); + assertEq(resolvedOrder.input.amount, 0); + } + + // Test multiple dutch outputs get resolved correctly. + function testV3ResolveMultipleDutchOutputs() public { + uint256 currentBlock = 1659087340; + uint16 relativeEndBlock = 65535; + vm.roll(currentBlock); + + NonlinearDutchDecay[] memory curves = new NonlinearDutchDecay[](3); + curves[0] = CurveBuilder.singlePointCurve(relativeEndBlock, 100); + curves[1] = CurveBuilder.singlePointCurve(relativeEndBlock, 1000); + curves[2] = CurveBuilder.singlePointCurve(relativeEndBlock, 1000); + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: 1659029740, + deadline: 1659130540, + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0, 0), + outputs: OutputsBuilder.multipleV3Dutch( + address(tokenOut), Solarray.uint256s(1000, 10000, 2000), curves, address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs.length, 3); + assertEq(resolvedOrder.outputs[0].amount, 913); + assertEq(resolvedOrder.outputs[1].amount, 9122); + assertEq(resolvedOrder.outputs[2].amount, 1122); + assertEq(resolvedOrder.input.amount, 0); + } + + // Test that when decayStartBlock = now, that the output = startAmount + function testV3ResolveStartBlockEqualsNow() public { + uint256 currentBlock = 1659029740; + uint16 relativeEndBlock = 65535; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: currentBlock, + 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, 1000); + assertEq(resolvedOrder.outputs.length, 1); + assertEq(resolvedOrder.input.amount, 0); + } + + // At block 99, output will still be 1000. One block later at 100 (1% of 10k), + // the first decay will occur and the output will be 999. + function testV3ResolveFirstDecay() public { + uint256 startBlock = 0; + uint256 currentBlock = 99; // 1659030395 - 1659029740 = 655 = 0.00999 * 65535 + uint16 relativeEndBlock = 10000; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: startBlock, + 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, 1000); + + vm.roll(currentBlock + 1); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 999); + } + + function testV3FuzzPositiveDecayNeverOutOfBounds( + uint128 currentBlock, + uint128 decayStartBlock, + uint256 startAmount, + uint16 decayDuration, + uint256 decayAmount + ) public { + vm.assume(decayAmount < 2 ** 255 - 1); + vm.assume(startAmount <= UINT256_MAX - decayAmount); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: uint256(currentBlock), + startBlock: uint256(decayStartBlock), + deadline: type(uint256).max, + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(decayDuration, 0), 0, 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + startAmount, + startAmount, + CurveBuilder.singlePointCurve(decayDuration, 0 - int256(decayAmount)), + address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertGe(resolvedOrder.outputs[0].amount, startAmount); + uint256 endAmount = startAmount + decayAmount; + assertLe(resolvedOrder.outputs[0].amount, endAmount); + } + + function testV3FuzzNegativeDecayNeverOutOfBounds( + uint128 currentBlock, + uint128 decayStartBlock, + uint256 startAmount, + uint16 decayDuration, + uint256 decayAmount + ) public { + vm.assume(decayAmount < 2 ** 255 - 1); + // can't have neg prices + vm.assume(startAmount >= decayAmount); + vm.assume(startAmount <= UINT256_MAX - decayAmount); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: uint256(currentBlock), + startBlock: uint256(decayStartBlock), + deadline: type(uint256).max, + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(decayDuration, 0), 0, 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + startAmount, + 0, + CurveBuilder.singlePointCurve(decayDuration, int256(decayAmount)), + address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertLe(resolvedOrder.outputs[0].amount, startAmount); + uint256 endAmount = startAmount.sub(int256(decayAmount)); + assertGe(resolvedOrder.outputs[0].amount, endAmount); + } + + function testV3ResolveMultiPointInputDecay() public { + uint256 currentBlock = 1000; + uint256 decayStartBlock = currentBlock + 100; + vm.roll(currentBlock); + + uint16[] memory blocks = new uint16[](3); + blocks[0] = 100; + blocks[1] = 200; + blocks[2] = 300; + int256[] memory decayAmounts = new int256[](3); + decayAmounts[0] = -1 ether; // 2 ether + decayAmounts[1] = 0 ether; // 1 ether + decayAmounts[2] = 1 ether; // 0 ether + NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: decayStartBlock, + deadline: currentBlock + 400, + input: V3DutchInput(tokenIn, 1 ether, curve, 2 ether, 0), + outputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1000, 1000, CurveBuilder.emptyCurve(), address(0)) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 1 ether); + + // decay start block + vm.roll(decayStartBlock); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 1 ether); + + // halfway through first decay + vm.roll(decayStartBlock + 50); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 1.5 ether); + + // 20% through second decay + vm.roll(decayStartBlock + 120); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 1.8 ether); + + // 70% through third decay + vm.roll(decayStartBlock + 270); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 0.3 ether); + + // after last decay (before deadline) + vm.roll(decayStartBlock + 305); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 0 ether); + } + + function testV3ResolveMultiPointOutputDecay() public { + uint256 currentBlock = 1000; + uint256 decayStartBlock = currentBlock + 100; + vm.roll(currentBlock); + + uint16[] memory blocks = new uint16[](3); + blocks[0] = 100; + blocks[1] = 200; + blocks[2] = 300; + int256[] memory decayAmounts = new int256[](3); + decayAmounts[0] = -1000; // 2000 + decayAmounts[1] = 0; // 1000 + decayAmounts[2] = 1000; // 0 + NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: decayStartBlock, + deadline: currentBlock + 400, + input: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether, 0), + outputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1000, 0, curve, address(0)) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 1 ether); + + // decay start block + vm.roll(decayStartBlock); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 1 ether); + + // halfway through first decay + vm.roll(decayStartBlock + 50); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1500); + assertEq(resolvedOrder.input.amount, 1 ether); + + // 20% through second decay + vm.roll(decayStartBlock + 120); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1800); + assertEq(resolvedOrder.input.amount, 1 ether); + + // 70% through third decay + vm.roll(decayStartBlock + 270); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 300); + assertEq(resolvedOrder.input.amount, 1 ether); + + // after last decay (before deadline) + vm.roll(decayStartBlock + 305); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 1 ether); + } + + function testV3ResolveMultiPointMultiOutputDecay() public { + uint256 currentBlock = 1000; + uint256 decayStartBlock = currentBlock + 100; + vm.roll(currentBlock); + + // Two output tokens + V3DutchOutput[] memory outputs = new V3DutchOutput[](2); + uint16[] memory blocks = new uint16[](3); + blocks[0] = 100; + blocks[1] = 200; + blocks[2] = 300; + int256[] memory decayAmounts = new int256[](3); + decayAmounts[0] = -1000; // 2000 + decayAmounts[1] = 0; // 1000 + decayAmounts[2] = 1000; // 0 + NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); + outputs[0] = OutputsBuilder.singleV3Dutch(address(tokenOut), 1000, 0, curve, address(0))[0]; + + // Second token does not decay + outputs[1] = OutputsBuilder.singleV3Dutch(address(tokenOut2), 1000, 0, CurveBuilder.emptyCurve(), address(0))[0]; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: decayStartBlock, + deadline: currentBlock + 400, + input: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether, 0), + outputs: outputs + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.outputs[1].amount, 1000); + assertEq(resolvedOrder.input.amount, 1 ether); + + // decay start block + vm.roll(decayStartBlock); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.outputs[1].amount, 1000); + assertEq(resolvedOrder.input.amount, 1 ether); + + // halfway through first decay + vm.roll(decayStartBlock + 50); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1500); + assertEq(resolvedOrder.outputs[1].amount, 1000); + assertEq(resolvedOrder.input.amount, 1 ether); + + // 20% through second decay + vm.roll(decayStartBlock + 120); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1800); + assertEq(resolvedOrder.outputs[1].amount, 1000); + assertEq(resolvedOrder.input.amount, 1 ether); + + // 70% through third decay + vm.roll(decayStartBlock + 270); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 300); + assertEq(resolvedOrder.outputs[1].amount, 1000); + assertEq(resolvedOrder.input.amount, 1 ether); + + // after last decay (before deadline) + vm.roll(decayStartBlock + 305); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.outputs[1].amount, 1000); + assertEq(resolvedOrder.input.amount, 1 ether); + } + + /* Gas adjustment tests */ + + function testV3ResolveNoGasAdjustment() public { + uint256 currentBlock = 1000; + vm.roll(currentBlock); + vm.fee(1 gwei); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: currentBlock + 100, + deadline: currentBlock + 200, + input: V3DutchInput(tokenIn, 1 ether, CurveBuilder.singlePointCurve(200, 1 ether), 2 ether, 1 gwei), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 0, 0, CurveBuilder.singlePointCurve(200, -100), address(0) + ) + }) + ); + + // Unchanged basefee + vm.fee(1 gwei); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 1 ether); + } + + function testV3ResolveInputGasAdjustment() public { + uint256 currentBlock = 1000; + uint256 decayStartBlock = currentBlock + 100; + vm.roll(currentBlock); + vm.fee(1 gwei); + + uint16[] memory blocks = new uint16[](3); + blocks[0] = 100; + blocks[1] = 200; + blocks[2] = 300; + int256[] memory decayAmounts = new int256[](3); + decayAmounts[0] = -1 ether; // 2 ether + decayAmounts[1] = 0; // 1 ether + decayAmounts[2] = 1 ether; // 0 ether + NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: decayStartBlock, + deadline: currentBlock + 500, + input: V3DutchInput(tokenIn, 1 ether, curve, 2 ether, 1 gwei), + outputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 0, 0, CurveBuilder.emptyCurve(), address(0)) + }) + ); + + // +1 gwei basefee + vm.fee(2 gwei); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 1 ether + 1 gwei); + + // block progression and +2 gwei basefee + vm.fee(3 gwei); + vm.roll(decayStartBlock + 50); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 1.5 ether + 2 gwei); + + // block progression but input capped at max amount (2 ether) + vm.fee(2 gwei); + vm.roll(decayStartBlock + 100); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 2 ether); + + // block progression and +1 gwei basefee + vm.fee(2 gwei); + vm.roll(decayStartBlock + 120); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 1.8 ether + 1 gwei); + + // block progression and -1 gwei basefee + vm.fee(0 gwei); + vm.roll(decayStartBlock + 200); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 1 ether - 1 gwei); + + // block progression and -.5 gwei basefee + vm.fee(0.5 gwei); + vm.roll(decayStartBlock + 200); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 1 ether - 0.5 gwei); + + // block progression and -0 gwei basefee + vm.fee(1 gwei); + vm.roll(decayStartBlock + 250); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 0.5 ether); + + // block progression and -1 gwei basefee + vm.fee(0 gwei); + vm.roll(decayStartBlock + 300); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 0 ether); // capped at 0 + } + + function testV3ResolveOutputGasAdjustment() public { + uint256 currentBlock = 1000; + uint256 decayStartBlock = currentBlock + 100; + vm.roll(currentBlock); + vm.fee(1 gwei); + + uint16[] memory blocks = new uint16[](3); + blocks[0] = 100; + blocks[1] = 200; + blocks[2] = 300; + int256[] memory decayAmounts = new int256[](3); + decayAmounts[0] = -1 ether; // 2 ether + decayAmounts[1] = 0; // 1 ether + decayAmounts[2] = 0.5 ether; // .5 ether + NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: decayStartBlock, + deadline: currentBlock + 500, + input: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether, 0), + outputs: OutputsBuilder.singleV3Dutch( + V3DutchOutput(address(tokenOut), 1 ether, curve, address(0), 0.5 ether, 1 gwei) + ) + }) + ); + + // +1 gwei basefee + vm.fee(2 gwei); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1 ether - 1 gwei); + assertEq(resolvedOrder.input.amount, 1 ether); + + // block progression and +2 gwei basefee + vm.fee(3 gwei); + vm.roll(decayStartBlock + 50); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1.5 ether - 2 gwei); + assertEq(resolvedOrder.input.amount, 1 ether); + + // block progression and +1 gwei basefee + vm.fee(2 gwei); + vm.roll(decayStartBlock + 100); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 2 ether - 1 gwei); + assertEq(resolvedOrder.input.amount, 1 ether); + + // block progression and +1 gwei basefee + vm.fee(2 gwei); + vm.roll(decayStartBlock + 120); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1.8 ether - 1 gwei); + assertEq(resolvedOrder.input.amount, 1 ether); + + // block progression and -1 gwei basefee + vm.fee(0 gwei); + vm.roll(decayStartBlock + 200); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1 ether + 1 gwei); + assertEq(resolvedOrder.input.amount, 1 ether); + + // block progression and -.5 gwei basefee + vm.fee(0.5 gwei); + vm.roll(decayStartBlock + 200); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1 ether + 0.5 gwei); + assertEq(resolvedOrder.input.amount, 1 ether); + + // block progression and -0 gwei basefee + vm.fee(1 gwei); + vm.roll(decayStartBlock + 250); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0.75 ether); + assertEq(resolvedOrder.input.amount, 1 ether); + + // block progression and +4 gwei basefee + vm.fee(5 gwei); + vm.roll(decayStartBlock + 300); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0.5 ether); // capped at .5 + assertEq(resolvedOrder.input.amount, 1 ether); + } + + function testV3ResolveInputGasAdjustmentBounded(uint64 fee, uint128 adjustment, uint256 blockNumber) public { + uint256 currentBlock = 1000; + uint256 decayStartBlock = currentBlock + 100; + vm.roll(currentBlock); + vm.fee(1 gwei); + + uint16[] memory blocks = new uint16[](3); + blocks[0] = 100; + blocks[1] = 200; + blocks[2] = 300; + int256[] memory decayAmounts = new int256[](3); + decayAmounts[0] = -1 ether; // 2 ether + decayAmounts[1] = 0; // 1 ether + decayAmounts[2] = 0.5 ether; // .5 ether + NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: decayStartBlock, + deadline: currentBlock + 500, + input: V3DutchInput(tokenIn, 1 ether, curve, 2 ether, adjustment), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), 0 ether, 0 ether, CurveBuilder.emptyCurve(), address(0) + ) + }) + ); + + vm.fee(fee); + vm.roll(blockNumber); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertLe(resolvedOrder.input.amount, 2 ether); + } + + function testV3ResolveOutputGasAdjustmentBounded(uint64 fee, uint128 adjustment, uint256 blockNumber) public { + uint256 currentBlock = 1000; + uint256 decayStartBlock = currentBlock + 100; + vm.roll(currentBlock); + vm.fee(1 gwei); + + uint16[] memory blocks = new uint16[](3); + blocks[0] = 100; + blocks[1] = 200; + blocks[2] = 300; + int256[] memory decayAmounts = new int256[](3); + decayAmounts[0] = -1 ether; // 2 ether + decayAmounts[1] = 0; // 1 ether + decayAmounts[2] = 0.5 ether; // .5 ether + NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: decayStartBlock, + deadline: currentBlock + 500, + input: V3DutchInput(tokenIn, 1 ether, curve, 1 ether, 0), + outputs: OutputsBuilder.singleV3Dutch( + V3DutchOutput(address(tokenOut), 1 ether, CurveBuilder.emptyCurve(), address(0), 0.5 ether, adjustment) + ) + }) + ); + + vm.fee(fee); + vm.roll(blockNumber); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertGe(resolvedOrder.outputs[0].amount, 0.5 ether); + } + + function testV3ResolveSmallGasAdjustments() public { + uint256 currentBlock = 1000; + vm.roll(currentBlock); + vm.fee(1 gwei); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: currentBlock + 100, + deadline: currentBlock + 200, + input: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 2 ether, 1), + outputs: OutputsBuilder.singleV3Dutch( + V3DutchOutput(address(tokenOut), 1 ether, CurveBuilder.emptyCurve(), address(0), 0.5 ether, 1) + ) + }) + ); + + // +1 gwei basefee = 1 wei change + vm.fee(2 gwei); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1 ether - 1); + assertEq(resolvedOrder.input.amount, 1 ether + 1); + + // -1 gwei basefee = 1 wei change + vm.fee(0 gwei); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1 ether + 1); + assertEq(resolvedOrder.input.amount, 1 ether - 1); + } + + function testV3ResolveLargeGasAdjustments() public { + uint256 currentBlock = 1000; + vm.roll(currentBlock); + vm.fee(1 gwei); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: currentBlock + 100, + deadline: currentBlock + 200, + input: V3DutchInput( + tokenIn, type(uint128).max, CurveBuilder.emptyCurve(), type(uint256).max, type(uint128).max + ), + outputs: OutputsBuilder.singleV3Dutch( + V3DutchOutput( + address(tokenOut), type(uint128).max, CurveBuilder.emptyCurve(), address(0), 0, type(uint128).max + ) + ) + }) + ); + + // +1 gwei basefee = type(uint128).max change + vm.fee(2 gwei); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 2 * uint256(type(uint128).max)); + + // -1 gwei basefee = type(uint128).max change + vm.fee(0 gwei); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 2 * uint256(type(uint128).max)); + assertEq(resolvedOrder.input.amount, 0); + } + + // Test multiple dutch outputs are gas adjusted correctly. + function testV3ResolveMultipleDutchOutputsWithGasAdjustments() public { + uint256 currentBlock = 1659087340; + uint16 relativeEndBlock = 65535; + vm.roll(currentBlock); + vm.fee(1 gwei); + + NonlinearDutchDecay[] memory curves = new NonlinearDutchDecay[](3); + curves[0] = CurveBuilder.singlePointCurve(relativeEndBlock, 100); + curves[1] = CurveBuilder.singlePointCurve(relativeEndBlock, 1000); + curves[2] = CurveBuilder.singlePointCurve(relativeEndBlock, 1000); + + V3DutchOutput[] memory outputs = new V3DutchOutput[](3); + outputs[0] = V3DutchOutput(address(tokenOut), 1000, curves[0], address(0), 0, 1); + outputs[1] = V3DutchOutput(address(tokenOut), 10000, curves[1], address(0), 0, 1); + outputs[2] = V3DutchOutput(address(tokenOut), 2000, curves[2], address(0), 0, 1); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: 1659029740, + deadline: 1659130540, + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0, 0), + outputs: outputs + }) + ); + vm.fee(2 gwei); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs.length, 3); + assertEq(resolvedOrder.outputs[0].amount, 913 - 1); + assertEq(resolvedOrder.outputs[1].amount, 9122 - 1); + assertEq(resolvedOrder.outputs[2].amount, 1122 - 1); + assertEq(resolvedOrder.input.amount, 0); + + vm.fee(0 gwei); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs.length, 3); + assertEq(resolvedOrder.outputs[0].amount, 913 + 1); + assertEq(resolvedOrder.outputs[1].amount, 9122 + 1); + assertEq(resolvedOrder.outputs[2].amount, 1122 + 1); + assertEq(resolvedOrder.input.amount, 0); + } + + /* Test helpers */ + + struct TestDutchOrderSpec { + uint256 currentBlock; + uint256 startBlock; + uint256 deadline; + V3DutchInput input; + V3DutchOutput[] outputs; + } + + /// @dev Create a signed order and return the order and orderHash + /// @param request Order to sign + function createAndSignDutchOrder(V3DutchOrder memory request) + public + virtual + returns (SignedOrder memory signedOrder, bytes32 orderHash) + { + orderHash = request.hash(); + return (SignedOrder(abi.encode(request), signOrder(swapperPrivateKey, address(permit2), request)), orderHash); + } + + function generateOrder(TestDutchOrderSpec memory spec) internal returns (SignedOrder memory order) { + tokenIn.mint(address(swapper), uint256(spec.input.maxAmount)); + tokenIn.forceApprove(swapper, address(permit2), spec.input.maxAmount); + + uint256[] memory outputAmounts = new uint256[](spec.outputs.length); + for (uint256 i = 0; i < spec.outputs.length; i++) { + outputAmounts[i] = 0; + } + CosignerData memory cosignerData = CosignerData({ + decayStartBlock: spec.startBlock, + exclusiveFiller: address(0), + exclusivityOverrideBps: 0, + inputAmount: 0, + outputAmounts: outputAmounts + }); + V3DutchOrder memory request = V3DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withDeadline(spec.deadline).withSwapper(address(swapper)), + cosigner: vm.addr(cosignerPrivateKey), + baseInput: spec.input, + baseOutputs: spec.outputs, + cosignerData: cosignerData, + cosignature: bytes(""), + startingBaseFee: block.basefee + }); + bytes32 orderHash = request.hash(); + request.cosignature = cosignOrder(orderHash, cosignerData); + (order,) = createAndSignDutchOrder(request); + } + + function cosignOrder(bytes32 orderHash, CosignerData memory cosignerData) private pure returns (bytes memory sig) { + bytes32 msgHash = keccak256(abi.encodePacked(orderHash, abi.encode(cosignerData))); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(cosignerPrivateKey, msgHash); + sig = bytes.concat(r, s, bytes1(v)); + } + + function generateSignedOrders(V3DutchOrder[] memory orders) private view returns (SignedOrder[] memory result) { + result = new SignedOrder[](orders.length); + for (uint256 i = 0; i < orders.length; i++) { + bytes memory sig = signOrder(swapperPrivateKey, address(permit2), orders[i]); + result[i] = SignedOrder(abi.encode(orders[i]), sig); + } + } +} diff --git a/test/util/ArrayBuilder.sol b/test/util/ArrayBuilder.sol index 5583fdb9..58ffcf2e 100644 --- a/test/util/ArrayBuilder.sol +++ b/test/util/ArrayBuilder.sol @@ -28,6 +28,26 @@ library ArrayBuilder { } } + /// @dev Fill an int256[] with a single value + /// @param length uint256 + /// @param amount int256 + function fillInt(uint256 length, int256 amount) internal pure returns (int256[] memory amounts) { + amounts = new int256[](length); + for (uint256 i = 0; i < length; ++i) { + amounts[i] = amount; + } + } + + /// @dev Fill an uint16[] with a single value + /// @param length uint256 + /// @param amount uint16 + function fillUint16(uint256 length, uint16 amount) internal pure returns (uint16[] memory amounts) { + amounts = new uint16[](length); + for (uint256 i = 0; i < length; ++i) { + amounts[i] = amount; + } + } + /// @dev Set the value at index `i` in a to b /// @param a uint256[][] /// @param i uint256 diff --git a/test/util/CurveBuilder.sol b/test/util/CurveBuilder.sol new file mode 100644 index 00000000..05c2af38 --- /dev/null +++ b/test/util/CurveBuilder.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {ArrayBuilder} from "./ArrayBuilder.sol"; +import {toUint256} from "../../src/types/Uint16Array.sol"; +import {NonlinearDutchDecay} from "../../src/lib/V3DutchOrderLib.sol"; + +library CurveBuilder { + function emptyCurve() internal pure returns (NonlinearDutchDecay memory) { + return NonlinearDutchDecay({ + relativeBlocks: toUint256(ArrayBuilder.fillUint16(0, 0)), + relativeAmounts: ArrayBuilder.fillInt(0, 0) + }); + } + + function singlePointCurve(uint16 relativeBlock, int256 relativeAmount) + internal + pure + returns (NonlinearDutchDecay memory) + { + return NonlinearDutchDecay({ + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, relativeBlock)), + relativeAmounts: ArrayBuilder.fillInt(1, relativeAmount) + }); + } + + function multiPointCurve(uint16[] memory relativeBlocks, int256[] memory relativeAmounts) + internal + pure + returns (NonlinearDutchDecay memory) + { + return NonlinearDutchDecay({relativeBlocks: toUint256(relativeBlocks), relativeAmounts: relativeAmounts}); + } +} diff --git a/test/util/OutputsBuilder.sol b/test/util/OutputsBuilder.sol index 0abd0b5f..54451761 100644 --- a/test/util/OutputsBuilder.sol +++ b/test/util/OutputsBuilder.sol @@ -5,6 +5,9 @@ import {MockERC20} from "../util/mock/MockERC20.sol"; import {OutputToken} from "../../src/base/ReactorStructs.sol"; import {DutchOutput} from "../../src/reactors/DutchOrderReactor.sol"; import {PriorityOutput} from "../../src/lib/PriorityOrderLib.sol"; +import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../../src/lib/V3DutchOrderLib.sol"; +import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol"; +import {ArrayBuilder} from "../util/ArrayBuilder.sol"; library OutputsBuilder { function single(address token, uint256 amount, address recipient) internal pure returns (OutputToken[] memory) { @@ -88,4 +91,33 @@ library OutputsBuilder { } return result; } + + function singleV3Dutch(V3DutchOutput memory output) internal pure returns (V3DutchOutput[] memory) { + V3DutchOutput[] memory result = new V3DutchOutput[](1); + result[0] = output; + return result; + } + + function singleV3Dutch( + address token, + uint256 amount, + uint256 minAmount, + NonlinearDutchDecay memory curve, + address recipient + ) internal pure returns (V3DutchOutput[] memory) { + return singleV3Dutch(V3DutchOutput(token, amount, curve, recipient, minAmount, 0)); + } + + function multipleV3Dutch( + address token, + uint256[] memory amounts, + NonlinearDutchDecay[] memory curves, + address recipient + ) internal pure returns (V3DutchOutput[] memory) { + V3DutchOutput[] memory result = new V3DutchOutput[](amounts.length); + for (uint256 i = 0; i < amounts.length; i++) { + result[i] = V3DutchOutput(token, amounts[i], curves[i], recipient, 0, 0); + } + return result; + } } diff --git a/test/util/PermitSignature.sol b/test/util/PermitSignature.sol index 338cc794..13517fce 100644 --- a/test/util/PermitSignature.sol +++ b/test/util/PermitSignature.sol @@ -9,6 +9,7 @@ import {LimitOrder, LimitOrderLib} from "../../src/lib/LimitOrderLib.sol"; import {DutchOrder, DutchOrderLib} from "../../src/lib/DutchOrderLib.sol"; import {ExclusiveDutchOrder, ExclusiveDutchOrderLib} from "../../src/lib/ExclusiveDutchOrderLib.sol"; import {V2DutchOrder, V2DutchOrderLib} from "../../src/lib/V2DutchOrderLib.sol"; +import {V3DutchOrder, V3DutchOrderLib} from "../../src/lib/V3DutchOrderLib.sol"; import {PriorityOrder, PriorityOrderLib} from "../../src/lib/PriorityOrderLib.sol"; import {OrderInfo, InputToken} from "../../src/base/ReactorStructs.sol"; @@ -18,6 +19,7 @@ contract PermitSignature is Test { using ExclusiveDutchOrderLib for ExclusiveDutchOrder; using V2DutchOrderLib for V2DutchOrder; using PriorityOrderLib for PriorityOrder; + using V3DutchOrderLib for V3DutchOrder; bytes32 public constant NAME_HASH = keccak256("Permit2"); bytes32 public constant TYPE_HASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); @@ -41,6 +43,9 @@ contract PermitSignature is Test { bytes32 constant PRIORITY_ORDER_TYPE_HASH = keccak256(abi.encodePacked(TYPEHASH_STUB, PriorityOrderLib.PERMIT2_ORDER_TYPE)); + bytes32 constant V3_DUTCH_ORDER_TYPE_HASH = + keccak256(abi.encodePacked(TYPEHASH_STUB, V3DutchOrderLib.PERMIT2_ORDER_TYPE)); + function getPermitSignature( uint256 privateKey, address permit2, @@ -160,6 +165,22 @@ contract PermitSignature is Test { ); } + function signOrder(uint256 privateKey, address permit2, V3DutchOrder memory order) + internal + view + returns (bytes memory sig) + { + return signOrder( + privateKey, + permit2, + order.info, + address(order.baseInput.token), + order.baseInput.maxAmount, + V3_DUTCH_ORDER_TYPE_HASH, + order.hash() + ); + } + function _domainSeparatorV4(address permit2) internal view returns (bytes32) { return keccak256(abi.encode(TYPE_HASH, NAME_HASH, block.chainid, permit2)); } diff --git a/test/util/mock/MockExclusivityLib.sol b/test/util/mock/MockExclusivityLib.sol index ef829dbf..ee6f860d 100644 --- a/test/util/mock/MockExclusivityLib.sol +++ b/test/util/mock/MockExclusivityLib.sol @@ -5,17 +5,31 @@ import {ResolvedOrder} from "../../../src/base/ReactorStructs.sol"; import {ExclusivityLib} from "../../../src/lib/ExclusivityLib.sol"; contract MockExclusivityLib { - function handleExclusiveOverride( + function handleExclusiveOverrideTimestamp( ResolvedOrder memory order, address exclusive, - uint256 exclusivityEndTime, + uint256 exclusivityEnd, uint256 exclusivityOverrideBps ) external view returns (ResolvedOrder memory) { - ExclusivityLib.handleExclusiveOverride(order, exclusive, exclusivityEndTime, exclusivityOverrideBps); + ExclusivityLib.handleExclusiveOverrideTimestamp(order, exclusive, exclusivityEnd, exclusivityOverrideBps); return order; } - function hasFillingRights(address exclusive, uint256 exclusivityEndTime) external view returns (bool pass) { - return ExclusivityLib.hasFillingRights(exclusive, exclusivityEndTime); + function handleExclusiveOverrideBlock( + ResolvedOrder memory order, + address exclusive, + uint256 exclusivityEnd, + uint256 exclusivityOverrideBps + ) external view returns (ResolvedOrder memory) { + ExclusivityLib.handleExclusiveOverrideBlock(order, exclusive, exclusivityEnd, exclusivityOverrideBps); + return order; + } + + function hasFillingRights(address exclusive, uint256 exclusivityEnd, uint256 currentPosition) + external + view + returns (bool pass) + { + return ExclusivityLib.hasFillingRights(exclusive, exclusivityEnd, currentPosition); } }