From 936eaeed31487aa59a9d3d1f013dc15a642ff878 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Fri, 16 Aug 2024 12:30:39 -0600 Subject: [PATCH 01/24] WIP --- src/base/ReactorStructs.sol | 2 +- src/lib/NonLinearDutchDecayLib.sol | 112 +++++++++++++ src/lib/NonLinearDutchOrderLib.sol | 165 ++++++++++++++++++++ src/lib/SafeMath.sol | 14 ++ src/reactors/NonLinearDutchOrderReactor.sol | 126 +++++++++++++++ yarn.lock | 4 + 6 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 src/lib/NonLinearDutchDecayLib.sol create mode 100644 src/lib/NonLinearDutchOrderLib.sol create mode 100644 src/lib/SafeMath.sol create mode 100644 src/reactors/NonLinearDutchOrderReactor.sol create mode 100644 yarn.lock diff --git a/src/base/ReactorStructs.sol b/src/base/ReactorStructs.sol index 2ab04a4c..ba17ff09 100644 --- a/src/base/ReactorStructs.sol +++ b/src/base/ReactorStructs.sol @@ -17,7 +17,7 @@ struct OrderInfo { address swapper; // The nonce of the order, allowing for signature replay protection and cancellation uint256 nonce; - // The timestamp after which this order is no longer valid + // The timestamp/blocknumber after which this order is no longer valid uint256 deadline; // Custom validation contract IValidationCallback additionalValidationContract; diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/NonLinearDutchDecayLib.sol new file mode 100644 index 00000000..adcccc85 --- /dev/null +++ b/src/lib/NonLinearDutchDecayLib.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {OutputToken, InputToken} from "../base/ReactorStructs.sol"; +import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../lib/NonLinearDutchOrderLib.sol"; +import {SafeMath} from "./SafeMath.sol"; +import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; + +/// @notice helpers for handling dutch order objects +library NonLinearDutchDecayLib { + using FixedPointMathLib for uint256; + + /// @notice thrown if the decay direction is incorrect + /// - for DutchInput, startAmount must be less than or equal to endAmount + /// - for DutchOutput, startAmount must be greater than or equal to endAmount + error IncorrectAmounts(); + + /// @notice thrown if the curve blocks are not strictly increasing + error InvalidDecay(); + + struct CurveSegment { + uint256 startAmount; + uint256 endAmount; + uint256 decayStartBlock; + uint256 decayEndBlock; + } + + /// @notice locates the surrounding points on the curve + function decay(NonLinearDecay memory curve, uint256 startAmount, uint256 decayStartBlock) + internal + view + returns (uint256 decayedAmount) + { + // handle current block before decay or no decay + if (decayStartBlock >= block.number || curve.relativeBlock.length == 0) { + return startAmount; + } + uint256 blockDelta = block.number - decayStartBlock; + // iterate through the points and locate the current segment + for (uint256 i = 0; i < curve.relativeBlock.length; i++) { + if (curve.relativeBlock[i] >= blockDelta) { + uint256 lastAmount = startAmount; + uint256 startBlock = decayStartBlock; + if (i != 0) { + // Ensure valid decay + if (curve.relativeBlock[i] <= curve.relativeBlock[i-1]) { + revert InvalidDecay(); + } + lastAmount = SafeMath.addIntToUint(startAmount, curve.relativeAmount[i-1]); + startBlock = curve.relativeBlock[i-1]; + } + uint256 nextAmount = SafeMath.addIntToUint(curve.relativeAmount[i], lastAmount); + // linear interpolation between the two points + unchecked { + uint256 elapsed = blockDelta - startBlock; + uint256 duration = curve.relativeBlock[i] - startBlock; + if (nextAmount < lastAmount) { + return lastAmount - (lastAmount - nextAmount).mulDivDown(elapsed, duration); + } else { + return lastAmount + (nextAmount - lastAmount).mulDivUp(elapsed, duration); + } + } + } + } + // handle current block after last decay block + decayedAmount = curve.relativeAmount[curve.relativeAmount.length - 1]; + } + + /// @notice returns a decayed output using the given dutch spec and times + /// @param output The output to decay + /// @param decayStartBlock The block to start decaying + /// @return result a decayed output + function decay(NonLinearDutchOutput memory output, uint256 decayStartBlock) + internal + view + returns (OutputToken memory result) + { + uint256 decayedOutput = NonLinearDutchDecayLib.decay(output.curve, output.startAmount, decayStartBlock); + result = OutputToken(output.token, decayedOutput, output.recipient); + } + + /// @notice returns a decayed output array using the given dutch spec and times + /// @param outputs The output array to decay + /// @param decayStartBlock The block to start decaying + /// @return result a decayed output array + function decay(NonLinearDutchOutput[] memory outputs, uint256 decayStartBlock) + internal + view + returns (OutputToken[] memory result) + { + uint256 outputLength = outputs.length; + result = new OutputToken[](outputLength); + unchecked { + 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(NonLinearDutchInput memory input, uint256 decayStartBlock) + internal + view + returns (InputToken memory result) + { + uint256 decayedInput = NonLinearDutchDecayLib.decay(input.curve, input.startAmount, decayStartBlock); + result = InputToken(input.token, decayedInput, input.endAmount); + } +} diff --git a/src/lib/NonLinearDutchOrderLib.sol b/src/lib/NonLinearDutchOrderLib.sol new file mode 100644 index 00000000..dc365dab --- /dev/null +++ b/src/lib/NonLinearDutchOrderLib.sol @@ -0,0 +1,165 @@ +// 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 NonLinearDutchOrder { + // generic order information + OrderInfo info; + // The address which must cosign the full order + address cosigner; + // The tokens that the swapper will provide when settling the order + NonLinearDutchInput baseInput; + // The tokens that must be received to satisfy the order + NonLinearDutchOutput[] 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 relativeBlock should be strictly increasing +struct NonLinearDecay { + uint256[] relativeBlock; + int256[] relativeAmount; +} + +/// @dev An amount of input tokens that increases non-linearly over time +struct NonLinearDutchInput { + // 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 + NonLinearDecay curve; +} + +/// @dev An amount of output tokens that decreases non-linearly over time +struct NonLinearDutchOutput { + // 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 + NonLinearDecay curve; + // The address who must receive the tokens to satisfy the order + address recipient; +} + +/// @notice helpers for handling custom curve order objects +library NonLinearDutchOrderLib { + using OrderInfoLib for OrderInfo; + + bytes internal constant NON_LINEAR_DUTCH_ORDER_TYPE = abi.encodePacked( + "NonLinearDutchOrder(", + "OrderInfo info,", + "address cosigner,", + "NonLinearDutchInput baseInput,", + "NonLinearDutchOutput[] baseOutputs)" + ); + bytes internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE = abi.encodePacked( + "NonLinearDutchOutput(", + "address token,", + "uint256 startAmount,", + "NonLinearDecay curve,", + "address recipient)" + ); + bytes32 internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH = keccak256(NON_LINEAR_DUTCH_OUTPUT_TYPE); + bytes internal constant NON_LINEAR_DECAY_TYPE = abi.encodePacked( + "NonLinearDecay(", + "uint256[] relativeBlock,", + "int256[] relativeAmount)" + ); + bytes32 internal constant NON_LINEAR_DECAY_TYPE_HASH = keccak256(NON_LINEAR_DECAY_TYPE); + + bytes internal constant ORDER_TYPE = + abi.encodePacked(NON_LINEAR_DECAY_TYPE, NON_LINEAR_DUTCH_ORDER_TYPE, NON_LINEAR_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( + "NonLinearDutchOrder witness)", + NON_LINEAR_DECAY_TYPE, + NON_LINEAR_DUTCH_ORDER_TYPE, + NON_LINEAR_DUTCH_OUTPUT_TYPE, + OrderInfoLib.ORDER_INFO_TYPE, + DutchOrderLib.TOKEN_PERMISSIONS_TYPE + ) + ); + + function hash(NonLinearDecay memory curve) internal pure returns (bytes32) { + return keccak256( + abi.encode( + NON_LINEAR_DECAY_TYPE_HASH, + keccak256(abi.encodePacked(curve.relativeBlock)), + keccak256(abi.encodePacked(curve.relativeAmount)) + ) + ); + } + + /// @notice hash the given output + /// @param output the output to hash + /// @return the eip-712 output hash + function hash(NonLinearDutchOutput memory output) internal pure returns (bytes32) { + return keccak256( + abi.encode( + NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH, + output.token, + output.startAmount, + output.curve.hash(), + output.recipient + ) + ); + } + + /// @notice hash the given outputs + /// @param outputs the outputs to hash + /// @return the eip-712 outputs hash + function hash(NonLinearDutchOutput[] memory outputs) internal pure returns (bytes32) { + unchecked { + bytes memory packedHashes = new bytes(32 * outputs.length); + + for (uint256 i = 0; i < outputs.length; 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(NonLinearDutchOrder memory order) internal pure returns (bytes32) { + return keccak256( + abi.encode( + ORDER_TYPE_HASH, + order.info.hash(), + order.cosigner, + order.baseInput.hash(), + order.baseOutputs.hash() + ) + ); + } +} diff --git a/src/lib/SafeMath.sol b/src/lib/SafeMath.sol new file mode 100644 index 00000000..ef9b85dc --- /dev/null +++ b/src/lib/SafeMath.sol @@ -0,0 +1,14 @@ +pragma solidity ^0.8.0; + +contract SafeMath { + function addIntToUint(int256 a, uint256 b) public pure returns (uint256) { + if (a < 0) { + // If a is negative, subtract its absolute value from b + require(b >= uint256(-a), "negative_uint"); + return b - uint256(-a); + } else { + // If a is positive, add it to b + return b + uint256(a); + } + } +} diff --git a/src/reactors/NonLinearDutchOrderReactor.sol b/src/reactors/NonLinearDutchOrderReactor.sol new file mode 100644 index 00000000..fac31e03 --- /dev/null +++ b/src/reactors/NonLinearDutchOrderReactor.sol @@ -0,0 +1,126 @@ +// 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 {NonLinearDutchOrderLib, NonLinearDutchOrder, CosignerData, NonLinearDutchOutput, NonLinearDutchInput} from "../lib/NonLinearDutchOrderLib.sol"; +import {SignedOrder, ResolvedOrder} from "../base/ReactorStructs.sol"; + +/// @notice Reactor for non-linear dutch orders +/// @dev V2 orders must be cosigned by the specified cosigner to override timings and starting values +/// @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 NonLinearDutchOrderReactor is BaseReactor { + using Permit2Lib for ResolvedOrder; + using NonLinearDutchOrderLib for NonLinearDutchOrder; + using NonLinearDutchDecayLib for NonLinearDutchOutput[]; + using NonLinearDutchDecayLib for NonLinearDutchInput; + using ExclusivityLib for ResolvedOrder; + + /// @notice thrown when an order's deadline is before its end block + error DeadlineBeforeEndBlock(); + + /// @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) + { + NonLinearDutchOrder memory order = abi.decode(signedOrder.order, (NonLinearDutchOrder)); + // 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); + + 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.handleExclusiveOverride( + order.cosignerData.exclusiveFiller, + order.cosignerData.decayStartTime, + 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, + NonLinearDutchOrderLib.PERMIT2_ORDER_TYPE, + order.sig + ); + } + + function _updateWithCosignerAmounts(NonLinearDutchOrder 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(); + } + for (uint256 i = 0; i < order.baseOutputs.length; i++) { + NonLinearDutchOutput memory output = order.baseOutputs[i]; + uint256 outputAmount = order.cosignerData.outputAmounts[i]; + if (outputAmount != 0) { + if (outputAmount < output.startAmount) { + revert InvalidCosignerOutput(); + } + output.startAmount = outputAmount; + } + } + } + + /// @notice validate the dutch order fields + /// - deadline must be greater than or equal to decayEndBlock + /// - if there's input decay, outputs must not decay + /// @dev Throws if the order is invalid + function _validateOrder(bytes32 orderHash, NonLinearDutchOrder memory order) internal pure { + uint256 relativeDecayEndBlock = order.info.relativeBlock.length == 0 + ? 0 + : order.info.relativeBlock[order.info.relativeBlock.length-1]; + if (order.info.deadline < order.cosignerData.decayStartBlock + relativeDecayEndBlock) { + revert DeadlineBeforeEndBlock(); + } + + (bytes32 r, bytes32 s) = abi.decode(order.cosignature, (bytes32, bytes32)); + uint8 v = uint8(order.cosignature[64]); + // cosigner signs over (orderHash || cosignerData) + address signer = ecrecover(keccak256(abi.encodePacked(orderHash, abi.encode(order.cosignerData))), v, r, s); + if (order.cosigner != signer || signer == address(0)) { + revert InvalidCosignature(); + } + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..fb57ccd1 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + From 2bca587e2a1cb018a8392dec71806a9211582574 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Mon, 19 Aug 2024 14:39:13 -0600 Subject: [PATCH 02/24] Builds successfully --- src/lib/ExclusivityLib.sol | 16 +++++++----- src/lib/NonLinearDutchDecayLib.sol | 13 +++------- src/lib/NonLinearDutchOrderLib.sol | 23 ++++++++++++++--- src/lib/SafeMath.sol | 2 +- src/reactors/ExclusiveDutchOrderReactor.sol | 2 +- src/reactors/NonLinearDutchOrderReactor.sol | 28 ++++++++++++++++----- src/reactors/V2DutchOrderReactor.sol | 3 ++- test/lib/ExclusivityLib.t.sol | 24 +++++++++--------- test/util/mock/MockExclusivityLib.sol | 11 ++++---- 9 files changed, 78 insertions(+), 44 deletions(-) diff --git a/src/lib/ExclusivityLib.sol b/src/lib/ExclusivityLib.sol index 35ef7ff7..ca35d222 100644 --- a/src/lib/ExclusivityLib.sol +++ b/src/lib/ExclusivityLib.sol @@ -20,16 +20,17 @@ 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/block /// @param exclusivityOverrideBps The exclusivity override BPS function handleExclusiveOverride( ResolvedOrder memory order, address exclusive, - uint256 exclusivityEndTime, - uint256 exclusivityOverrideBps + uint256 exclusivityEnd, + uint256 exclusivityOverrideBps, + bool timeBased ) internal view { // if the filler has fill right, we proceed with the order as-is - if (hasFillingRights(exclusive, exclusivityEndTime)) { + if (hasFillingRights(exclusive, exclusivityEnd, timeBased)) { return; } @@ -54,7 +55,10 @@ library ExclusivityLib { /// @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, bool timeBased) internal view returns (bool) { + return exclusive == address(0) || + (timeBased && block.timestamp > exclusivityEnd) || + (!timeBased && block.number > exclusivityEnd) || + exclusive == msg.sender; } } diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/NonLinearDutchDecayLib.sol index adcccc85..16272e5d 100644 --- a/src/lib/NonLinearDutchDecayLib.sol +++ b/src/lib/NonLinearDutchDecayLib.sol @@ -10,11 +10,6 @@ import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; library NonLinearDutchDecayLib { using FixedPointMathLib for uint256; - /// @notice thrown if the decay direction is incorrect - /// - for DutchInput, startAmount must be less than or equal to endAmount - /// - for DutchOutput, startAmount must be greater than or equal to endAmount - error IncorrectAmounts(); - /// @notice thrown if the curve blocks are not strictly increasing error InvalidDecay(); @@ -46,10 +41,10 @@ library NonLinearDutchDecayLib { if (curve.relativeBlock[i] <= curve.relativeBlock[i-1]) { revert InvalidDecay(); } - lastAmount = SafeMath.addIntToUint(startAmount, curve.relativeAmount[i-1]); + lastAmount = SafeMath.addIntToUint(curve.relativeAmount[i-1], startAmount); startBlock = curve.relativeBlock[i-1]; } - uint256 nextAmount = SafeMath.addIntToUint(curve.relativeAmount[i], lastAmount); + uint256 nextAmount = SafeMath.addIntToUint(curve.relativeAmount[i], startAmount); // linear interpolation between the two points unchecked { uint256 elapsed = blockDelta - startBlock; @@ -63,7 +58,7 @@ library NonLinearDutchDecayLib { } } // handle current block after last decay block - decayedAmount = curve.relativeAmount[curve.relativeAmount.length - 1]; + decayedAmount = SafeMath.addIntToUint(curve.relativeAmount[curve.relativeAmount.length - 1], startAmount); } /// @notice returns a decayed output using the given dutch spec and times @@ -107,6 +102,6 @@ library NonLinearDutchDecayLib { returns (InputToken memory result) { uint256 decayedInput = NonLinearDutchDecayLib.decay(input.curve, input.startAmount, decayStartBlock); - result = InputToken(input.token, decayedInput, input.endAmount); + result = InputToken(input.token, decayedInput, input.maxAmount); } } diff --git a/src/lib/NonLinearDutchOrderLib.sol b/src/lib/NonLinearDutchOrderLib.sol index dc365dab..f36b8022 100644 --- a/src/lib/NonLinearDutchOrderLib.sol +++ b/src/lib/NonLinearDutchOrderLib.sol @@ -49,6 +49,8 @@ struct NonLinearDutchInput { uint256 startAmount; // The amount of tokens at the each future block NonLinearDecay curve; + // The max amount of the curve + uint256 maxAmount; } /// @dev An amount of output tokens that decreases non-linearly over time @@ -115,6 +117,21 @@ library NonLinearDutchOrderLib { ); } + /// @notice hash the given input + /// @param input the input to hash + /// @return the eip-712 input hash + function hash(NonLinearDutchInput memory input) internal pure returns (bytes32) { + return keccak256( + abi.encode( + NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH, + input.token, + input.startAmount, + hash(input.curve), + input.maxAmount + ) + ); + } + /// @notice hash the given output /// @param output the output to hash /// @return the eip-712 output hash @@ -124,7 +141,7 @@ library NonLinearDutchOrderLib { NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH, output.token, output.startAmount, - output.curve.hash(), + hash(output.curve), output.recipient ) ); @@ -157,8 +174,8 @@ library NonLinearDutchOrderLib { ORDER_TYPE_HASH, order.info.hash(), order.cosigner, - order.baseInput.hash(), - order.baseOutputs.hash() + hash(order.baseInput), + hash(order.baseOutputs) ) ); } diff --git a/src/lib/SafeMath.sol b/src/lib/SafeMath.sol index ef9b85dc..9d5ecbd3 100644 --- a/src/lib/SafeMath.sol +++ b/src/lib/SafeMath.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.0; -contract SafeMath { +library SafeMath { function addIntToUint(int256 a, uint256 b) public pure returns (uint256) { if (a < 0) { // If a is negative, subtract its absolute value from b diff --git a/src/reactors/ExclusiveDutchOrderReactor.sol b/src/reactors/ExclusiveDutchOrderReactor.sol index b52a2f74..b130ac26 100644 --- a/src/reactors/ExclusiveDutchOrderReactor.sol +++ b/src/reactors/ExclusiveDutchOrderReactor.sol @@ -43,7 +43,7 @@ contract ExclusiveDutchOrderReactor is BaseReactor { sig: signedOrder.sig, hash: order.hash() }); - resolvedOrder.handleExclusiveOverride(order.exclusiveFiller, order.decayStartTime, order.exclusivityOverrideBps); + resolvedOrder.handleExclusiveOverride(order.exclusiveFiller, order.decayStartTime, order.exclusivityOverrideBps, true); } /// @inheritdoc BaseReactor diff --git a/src/reactors/NonLinearDutchOrderReactor.sol b/src/reactors/NonLinearDutchOrderReactor.sol index fac31e03..b04452c0 100644 --- a/src/reactors/NonLinearDutchOrderReactor.sol +++ b/src/reactors/NonLinearDutchOrderReactor.sol @@ -25,6 +25,9 @@ contract NonLinearDutchOrderReactor is BaseReactor { using NonLinearDutchDecayLib for NonLinearDutchInput; using ExclusivityLib for ResolvedOrder; + /// @notice thrown when the decay curve is missing + error MissingDecayCurve(); + /// @notice thrown when an order's deadline is before its end block error DeadlineBeforeEndBlock(); @@ -63,8 +66,9 @@ contract NonLinearDutchOrderReactor is BaseReactor { }); resolvedOrder.handleExclusiveOverride( order.cosignerData.exclusiveFiller, - order.cosignerData.decayStartTime, - order.cosignerData.exclusivityOverrideBps + order.cosignerData.decayStartBlock, + order.cosignerData.exclusivityOverrideBps, + false ); } @@ -104,13 +108,25 @@ contract NonLinearDutchOrderReactor is BaseReactor { } /// @notice validate the dutch order fields + /// - decay curves are defined /// - deadline must be greater than or equal to decayEndBlock - /// - if there's input decay, outputs must not decay /// @dev Throws if the order is invalid function _validateOrder(bytes32 orderHash, NonLinearDutchOrder memory order) internal pure { - uint256 relativeDecayEndBlock = order.info.relativeBlock.length == 0 - ? 0 - : order.info.relativeBlock[order.info.relativeBlock.length-1]; + if (order.baseInput.curve.relativeBlock.length == 0) { + revert MissingDecayCurve(); + } + for (uint256 i = 0; i < order.baseOutputs.length; i++) { + if (order.baseOutputs[i].curve.relativeBlock.length == 0) { + revert MissingDecayCurve(); + } + uint256 lastDecayPos = order.baseOutputs[i].curve.relativeBlock.length-1; + uint256 relativeOutputDecayEndBlock = order.baseOutputs[i].curve.relativeBlock[lastDecayPos]; + if (order.info.deadline < order.cosignerData.decayStartBlock + relativeOutputDecayEndBlock) { + revert DeadlineBeforeEndBlock(); + } + } + uint256 lastInputDecayPos = order.baseInput.curve.relativeBlock.length-1; + uint256 relativeDecayEndBlock = order.baseInput.curve.relativeBlock[lastInputDecayPos]; if (order.info.deadline < order.cosignerData.decayStartBlock + relativeDecayEndBlock) { revert DeadlineBeforeEndBlock(); } diff --git a/src/reactors/V2DutchOrderReactor.sol b/src/reactors/V2DutchOrderReactor.sol index a4d1c9b3..151017fe 100644 --- a/src/reactors/V2DutchOrderReactor.sol +++ b/src/reactors/V2DutchOrderReactor.sol @@ -64,7 +64,8 @@ contract V2DutchOrderReactor is BaseReactor { resolvedOrder.handleExclusiveOverride( order.cosignerData.exclusiveFiller, order.cosignerData.decayStartTime, - order.cosignerData.exclusivityOverrideBps + order.cosignerData.exclusivityOverrideBps, + true ); } diff --git a/test/lib/ExclusivityLib.t.sol b/test/lib/ExclusivityLib.t.sol index eae4ac02..20399c07 100644 --- a/test/lib/ExclusivityLib.t.sol +++ b/test/lib/ExclusivityLib.t.sol @@ -28,7 +28,7 @@ contract ExclusivityLibTest is Test { function testExclusivity(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, true), true); } function testExclusivityFail(address caller, address exclusive, uint256 nowTime, uint256 exclusiveTimestamp) @@ -39,20 +39,20 @@ contract ExclusivityLibTest is Test { vm.assume(exclusive != address(0)); vm.warp(nowTime); vm.prank(caller); - assertEq(exclusivity.hasFillingRights(exclusive, exclusiveTimestamp), false); + assertEq(exclusivity.hasFillingRights(exclusive, exclusiveTimestamp, true), false); } function testNoExclusivity(address caller, uint256 nowTime, uint256 exclusiveTimestamp) public { vm.warp(nowTime); vm.prank(caller); - assertEq(exclusivity.hasFillingRights(address(0), exclusiveTimestamp), true); + assertEq(exclusivity.hasFillingRights(address(0), exclusiveTimestamp, true), true); } function testExclusivityPeriodOver(address caller, uint256 nowTime, uint256 exclusiveTimestamp) public { vm.assume(nowTime > exclusiveTimestamp); vm.warp(nowTime); vm.prank(caller); - assertEq(exclusivity.hasFillingRights(address(1), exclusiveTimestamp), true); + assertEq(exclusivity.hasFillingRights(address(1), exclusiveTimestamp, true), true); } function testHandleExclusiveOverridePass(address exclusive, uint256 overrideAmt, uint128 amount) public { @@ -61,7 +61,7 @@ contract ExclusivityLibTest is Test { order.outputs = OutputsBuilder.single(token1, amount, recipient); vm.prank(exclusive); ResolvedOrder memory handled = - exclusivity.handleExclusiveOverride(order, exclusive, block.timestamp + 1, overrideAmt); + exclusivity.handleExclusiveOverride(order, exclusive, block.timestamp + 1, overrideAmt, true); // no changes assertEq(handled.outputs[0].amount, amount); assertEq(handled.outputs[0].recipient, recipient); @@ -73,7 +73,7 @@ contract ExclusivityLibTest is Test { order.outputs = OutputsBuilder.single(token1, amount, recipient); vm.prank(caller); ResolvedOrder memory handled = - exclusivity.handleExclusiveOverride(order, address(0), block.timestamp + 1, overrideAmt); + exclusivity.handleExclusiveOverride(order, address(0), block.timestamp + 1, overrideAmt, true); // no changes assertEq(handled.outputs[0].amount, amount); assertEq(handled.outputs[0].recipient, recipient); @@ -92,7 +92,7 @@ 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.handleExclusiveOverride(order, address(0), 99, overrideAmt, true); // no changes assertEq(handled.outputs[0].amount, amount); assertEq(handled.outputs[0].recipient, recipient); @@ -105,7 +105,7 @@ contract ExclusivityLibTest is Test { order.outputs = OutputsBuilder.single(token1, amount, recipient); vm.prank(caller); vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); - exclusivity.handleExclusiveOverride(order, exclusive, block.timestamp + 1, 0); + exclusivity.handleExclusiveOverride(order, exclusive, block.timestamp + 1, 0, true); } function testHandleExclusiveOverride() public { @@ -114,7 +114,7 @@ contract ExclusivityLibTest is Test { uint256 overrideAmt = 3000; vm.prank(address(2)); ResolvedOrder memory handled = - exclusivity.handleExclusiveOverride(order, address(1), block.timestamp + 1, overrideAmt); + exclusivity.handleExclusiveOverride(order, address(1), block.timestamp + 1, overrideAmt, true); // assert overrideAmt applied assertEq(handled.outputs[0].amount, 1.3 ether); assertEq(handled.outputs[0].recipient, recipient); @@ -126,7 +126,7 @@ contract ExclusivityLibTest is Test { uint256 overrideAmt = 3000; vm.prank(address(2)); ResolvedOrder memory handled = - exclusivity.handleExclusiveOverride(order, address(1), block.timestamp + 1, overrideAmt); + exclusivity.handleExclusiveOverride(order, address(1), block.timestamp + 1, overrideAmt, true); // assert overrideAmt applied assertEq(handled.outputs[0].amount, 1.3 ether + 2); assertEq(handled.outputs[0].recipient, recipient); @@ -142,7 +142,7 @@ 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.handleExclusiveOverride(order, exclusive, block.timestamp + 1, overrideAmt, true); // assert overrideAmt applied assertEq(handled.outputs[0].amount, uint256(amount).mulDivUp(10000 + overrideAmt, 10000)); assertEq(handled.outputs[0].recipient, recipient); @@ -166,7 +166,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.handleExclusiveOverride(order, exclusive, block.timestamp + 1, overrideAmt, true); // 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/util/mock/MockExclusivityLib.sol b/test/util/mock/MockExclusivityLib.sol index ef829dbf..264caa53 100644 --- a/test/util/mock/MockExclusivityLib.sol +++ b/test/util/mock/MockExclusivityLib.sol @@ -8,14 +8,15 @@ contract MockExclusivityLib { function handleExclusiveOverride( ResolvedOrder memory order, address exclusive, - uint256 exclusivityEndTime, - uint256 exclusivityOverrideBps + uint256 exclusivityEnd, + uint256 exclusivityOverrideBps, + bool timeBased ) external view returns (ResolvedOrder memory) { - ExclusivityLib.handleExclusiveOverride(order, exclusive, exclusivityEndTime, exclusivityOverrideBps); + ExclusivityLib.handleExclusiveOverride(order, exclusive, exclusivityEnd, exclusivityOverrideBps, timeBased); return order; } - function hasFillingRights(address exclusive, uint256 exclusivityEndTime) external view returns (bool pass) { - return ExclusivityLib.hasFillingRights(exclusive, exclusivityEndTime); + function hasFillingRights(address exclusive, uint256 exclusivityEnd, bool timeBased) external view returns (bool pass) { + return ExclusivityLib.hasFillingRights(exclusive, exclusivityEnd, timeBased); } } From c15bb0d121fe4abb3508f8838c378440a4db3b4f Mon Sep 17 00:00:00 2001 From: Cody Born Date: Wed, 21 Aug 2024 07:17:54 -0600 Subject: [PATCH 03/24] First set of tests --- ...veDutchOrder-BaseExecuteSingleWithFee.snap | 2 +- ...Base-ExclusiveDutchOrder-ExecuteBatch.snap | 2 +- ...utchOrder-ExecuteBatchMultipleOutputs.snap | 2 +- ...teBatchMultipleOutputsDifferentTokens.snap | 2 +- ...veDutchOrder-ExecuteBatchNativeOutput.snap | 2 +- ...ase-ExclusiveDutchOrder-ExecuteSingle.snap | 2 +- ...eDutchOrder-ExecuteSingleNativeOutput.snap | 2 +- ...iveDutchOrder-ExecuteSingleValidation.snap | 2 +- ...xclusiveDutchOrder-RevertInvalidNonce.snap | 2 +- ...V2DutchOrder-BaseExecuteSingleWithFee.snap | 2 +- .../Base-V2DutchOrder-ExclusiveFiller.snap | 2 +- .../Base-V2DutchOrder-ExecuteBatch.snap | 2 +- ...utchOrder-ExecuteBatchMultipleOutputs.snap | 2 +- ...teBatchMultipleOutputsDifferentTokens.snap | 2 +- ...V2DutchOrder-ExecuteBatchNativeOutput.snap | 2 +- .../Base-V2DutchOrder-ExecuteSingle.snap | 2 +- ...2DutchOrder-ExecuteSingleNativeOutput.snap | 2 +- ...-V2DutchOrder-ExecuteSingleValidation.snap | 2 +- .../Base-V2DutchOrder-InputOverride.snap | 2 +- .../Base-V2DutchOrder-OutputOverride.snap | 2 +- .../Base-V2DutchOrder-RevertInvalidNonce.snap | 2 +- ...omparisonTest-InterfaceAndProtocolFee.snap | 2 +- ...Test-InterfaceAndProtocolFeeEthOutput.snap | 2 +- ...colFeesGasComparisonTest-InterfaceFee.snap | 2 +- ...sComparisonTest-InterfaceFeeEthOutput.snap | 2 +- .../ProtocolFeesGasComparisonTest-NoFees.snap | 2 +- ...FeesGasComparisonTest-NoFeesEthOutput.snap | 2 +- src/lib/NonLinearDutchDecayLib.sol | 30 +++++-------------- src/lib/SafeMath.sol | 12 ++++---- test/util/ArrayBuilder.sol | 10 +++++++ 30 files changed, 51 insertions(+), 55 deletions(-) diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap index 06eecbc6..be96dd61 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -181708 \ No newline at end of file +181768 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap index 75b650c5..10e3d075 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -196661 \ No newline at end of file +196789 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap index 9cff91c6..1b794b03 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -206312 \ No newline at end of file +206440 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index 8eb9b64e..a1dce779 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -259868 \ No newline at end of file +259996 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap index e2cee2b1..c58a3fd4 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -190187 \ No newline at end of file +190315 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap index 462e130b..2b30de5b 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -148090 \ No newline at end of file +148154 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap index cf9cd5b1..cddd3bf4 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -133657 \ No newline at end of file +133721 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap index 3606dddc..3881d3b2 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -157405 \ No newline at end of file +157469 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-RevertInvalidNonce.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-RevertInvalidNonce.snap index 106fc79c..06c757f4 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-RevertInvalidNonce.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-RevertInvalidNonce.snap @@ -1 +1 @@ -26649 \ No newline at end of file +26713 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-V2DutchOrder-BaseExecuteSingleWithFee.snap index 4b1517ec..e4d22144 100644 --- a/.forge-snapshots/Base-V2DutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-V2DutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -188493 \ No newline at end of file +188553 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExclusiveFiller.snap b/.forge-snapshots/Base-V2DutchOrder-ExclusiveFiller.snap index 3f77dc49..8151ad14 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExclusiveFiller.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExclusiveFiller.snap @@ -1 +1 @@ -158636 \ No newline at end of file +158750 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatch.snap index 99cea989..a254d155 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -210334 \ No newline at end of file +210462 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputs.snap index ce9c1f0b..5169eb84 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -220559 \ No newline at end of file +220687 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index 63d45438..5b99d0e0 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -274686 \ No newline at end of file +274814 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchNativeOutput.snap index a2f04c92..e3d89d6a 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -203860 \ No newline at end of file +203988 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingle.snap index ab27c01b..6510c5fe 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -154872 \ No newline at end of file +154936 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleNativeOutput.snap index 8499cad8..5b9b837d 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -140434 \ No newline at end of file +140498 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleValidation.snap index 2218578a..304b3f43 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -164183 \ No newline at end of file +164247 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-InputOverride.snap b/.forge-snapshots/Base-V2DutchOrder-InputOverride.snap index 37745aea..bf78aba0 100644 --- a/.forge-snapshots/Base-V2DutchOrder-InputOverride.snap +++ b/.forge-snapshots/Base-V2DutchOrder-InputOverride.snap @@ -1 +1 @@ -158714 \ No newline at end of file +158778 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-OutputOverride.snap b/.forge-snapshots/Base-V2DutchOrder-OutputOverride.snap index dea3ba88..7796031e 100644 --- a/.forge-snapshots/Base-V2DutchOrder-OutputOverride.snap +++ b/.forge-snapshots/Base-V2DutchOrder-OutputOverride.snap @@ -1 +1 @@ -158663 \ No newline at end of file +158727 \ No newline at end of file diff --git a/.forge-snapshots/Base-V2DutchOrder-RevertInvalidNonce.snap b/.forge-snapshots/Base-V2DutchOrder-RevertInvalidNonce.snap index bdfe68ee..b4b34a35 100644 --- a/.forge-snapshots/Base-V2DutchOrder-RevertInvalidNonce.snap +++ b/.forge-snapshots/Base-V2DutchOrder-RevertInvalidNonce.snap @@ -1 +1 @@ -33446 \ No newline at end of file +33510 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap index c0977a90..beb26b1c 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap @@ -1 +1 @@ -176211 \ No newline at end of file +176271 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap index 4b4dcb44..abd11ca6 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap @@ -1 +1 @@ -162203 \ No newline at end of file +162263 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap index ef1eb938..064bdeaa 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap @@ -1 +1 @@ -165488 \ No newline at end of file +165548 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap index 4d6186b5..287dcf6c 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap @@ -1 +1 @@ -146315 \ No newline at end of file +146375 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap index b5ee7c88..2f01cb79 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap @@ -1 +1 @@ -148895 \ No newline at end of file +148955 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap index 44bd11d7..26bb66dd 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap @@ -1 +1 @@ -124557 \ No newline at end of file +124617 \ No newline at end of file diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/NonLinearDutchDecayLib.sol index 16272e5d..0c76f1aa 100644 --- a/src/lib/NonLinearDutchDecayLib.sol +++ b/src/lib/NonLinearDutchDecayLib.sol @@ -10,16 +10,6 @@ import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; library NonLinearDutchDecayLib { using FixedPointMathLib for uint256; - /// @notice thrown if the curve blocks are not strictly increasing - error InvalidDecay(); - - struct CurveSegment { - uint256 startAmount; - uint256 endAmount; - uint256 decayStartBlock; - uint256 decayEndBlock; - } - /// @notice locates the surrounding points on the curve function decay(NonLinearDecay memory curve, uint256 startAmount, uint256 decayStartBlock) internal @@ -27,7 +17,7 @@ library NonLinearDutchDecayLib { returns (uint256 decayedAmount) { // handle current block before decay or no decay - if (decayStartBlock >= block.number || curve.relativeBlock.length == 0) { + if (decayStartBlock >= block.number) { return startAmount; } uint256 blockDelta = block.number - decayStartBlock; @@ -35,20 +25,16 @@ library NonLinearDutchDecayLib { for (uint256 i = 0; i < curve.relativeBlock.length; i++) { if (curve.relativeBlock[i] >= blockDelta) { uint256 lastAmount = startAmount; - uint256 startBlock = decayStartBlock; + uint256 relativeStartBlock = 0; if (i != 0) { - // Ensure valid decay - if (curve.relativeBlock[i] <= curve.relativeBlock[i-1]) { - revert InvalidDecay(); - } - lastAmount = SafeMath.addIntToUint(curve.relativeAmount[i-1], startAmount); - startBlock = curve.relativeBlock[i-1]; + lastAmount = SafeMath.subIntFromUint(curve.relativeAmount[i-1], startAmount); + relativeStartBlock = curve.relativeBlock[i-1]; } - uint256 nextAmount = SafeMath.addIntToUint(curve.relativeAmount[i], startAmount); + uint256 nextAmount = SafeMath.subIntFromUint(curve.relativeAmount[i], startAmount); // linear interpolation between the two points unchecked { - uint256 elapsed = blockDelta - startBlock; - uint256 duration = curve.relativeBlock[i] - startBlock; + uint256 elapsed = blockDelta - relativeStartBlock; + uint256 duration = curve.relativeBlock[i] - relativeStartBlock; if (nextAmount < lastAmount) { return lastAmount - (lastAmount - nextAmount).mulDivDown(elapsed, duration); } else { @@ -58,7 +44,7 @@ library NonLinearDutchDecayLib { } } // handle current block after last decay block - decayedAmount = SafeMath.addIntToUint(curve.relativeAmount[curve.relativeAmount.length - 1], startAmount); + decayedAmount = SafeMath.subIntFromUint(curve.relativeAmount[curve.relativeAmount.length - 1], startAmount); } /// @notice returns a decayed output using the given dutch spec and times diff --git a/src/lib/SafeMath.sol b/src/lib/SafeMath.sol index 9d5ecbd3..67228ec0 100644 --- a/src/lib/SafeMath.sol +++ b/src/lib/SafeMath.sol @@ -1,14 +1,14 @@ pragma solidity ^0.8.0; library SafeMath { - function addIntToUint(int256 a, uint256 b) public pure returns (uint256) { + function subIntFromUint(int256 a, uint256 b) public pure returns (uint256) { if (a < 0) { - // If a is negative, subtract its absolute value from b - require(b >= uint256(-a), "negative_uint"); - return b - uint256(-a); + // If a is negative, add its absolute value to b + return b + uint256(-a); } else { - // If a is positive, add it to b - return b + uint256(a); + // If a is positive, subtract it from b + require(b >= uint256(a), "negative_uint"); + return b - uint256(a); } } } diff --git a/test/util/ArrayBuilder.sol b/test/util/ArrayBuilder.sol index 5583fdb9..16b095fd 100644 --- a/test/util/ArrayBuilder.sol +++ b/test/util/ArrayBuilder.sol @@ -28,6 +28,16 @@ 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 Set the value at index `i` in a to b /// @param a uint256[][] /// @param i uint256 From a8867dc4d14dde4475d6471734e175f250282139 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Thu, 22 Aug 2024 15:35:45 -0600 Subject: [PATCH 04/24] Unit tests + array packing --- src/lib/NonLinearDutchDecayLib.sol | 20 +-- src/lib/NonLinearDutchOrderLib.sol | 10 +- src/lib/SafeMath.sol | 14 -- src/lib/Util.sol | 34 +++++ src/reactors/NonLinearDutchOrderReactor.sol | 23 +-- test/lib/NonLinearDutchDecayLib.t.sol | 160 ++++++++++++++++++++ test/util/ArrayBuilder.sol | 10 ++ 7 files changed, 229 insertions(+), 42 deletions(-) delete mode 100644 src/lib/SafeMath.sol create mode 100644 src/lib/Util.sol create mode 100644 test/lib/NonLinearDutchDecayLib.t.sol diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/NonLinearDutchDecayLib.sol index 0c76f1aa..f379ecd8 100644 --- a/src/lib/NonLinearDutchDecayLib.sol +++ b/src/lib/NonLinearDutchDecayLib.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {OutputToken, InputToken} from "../base/ReactorStructs.sol"; import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../lib/NonLinearDutchOrderLib.sol"; -import {SafeMath} from "./SafeMath.sol"; +import {Util} from "./Util.sol"; import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; /// @notice helpers for handling dutch order objects @@ -22,19 +22,21 @@ library NonLinearDutchDecayLib { } uint256 blockDelta = block.number - decayStartBlock; // iterate through the points and locate the current segment - for (uint256 i = 0; i < curve.relativeBlock.length; i++) { - if (curve.relativeBlock[i] >= blockDelta) { + for (uint256 i = 0; i < curve.relativeAmount.length; i++) { + // relativeBlocks is and array of uint16 packed one uint256 + uint16 relativeBlock = Util.getUint16FromPacked(curve.relativeBlocks, i); + if (relativeBlock >= blockDelta) { uint256 lastAmount = startAmount; - uint256 relativeStartBlock = 0; + uint16 relativeStartBlock = 0; if (i != 0) { - lastAmount = SafeMath.subIntFromUint(curve.relativeAmount[i-1], startAmount); - relativeStartBlock = curve.relativeBlock[i-1]; + lastAmount = Util.subIntFromUint(curve.relativeAmount[i-1], startAmount); + relativeStartBlock = Util.getUint16FromPacked(curve.relativeBlocks, i-1); } - uint256 nextAmount = SafeMath.subIntFromUint(curve.relativeAmount[i], startAmount); + uint256 nextAmount = Util.subIntFromUint(curve.relativeAmount[i], startAmount); // linear interpolation between the two points unchecked { uint256 elapsed = blockDelta - relativeStartBlock; - uint256 duration = curve.relativeBlock[i] - relativeStartBlock; + uint256 duration = relativeBlock - relativeStartBlock; if (nextAmount < lastAmount) { return lastAmount - (lastAmount - nextAmount).mulDivDown(elapsed, duration); } else { @@ -44,7 +46,7 @@ library NonLinearDutchDecayLib { } } // handle current block after last decay block - decayedAmount = SafeMath.subIntFromUint(curve.relativeAmount[curve.relativeAmount.length - 1], startAmount); + decayedAmount = Util.subIntFromUint(curve.relativeAmount[curve.relativeAmount.length - 1], startAmount); } /// @notice returns a decayed output using the given dutch spec and times diff --git a/src/lib/NonLinearDutchOrderLib.sol b/src/lib/NonLinearDutchOrderLib.sol index f36b8022..c3387190 100644 --- a/src/lib/NonLinearDutchOrderLib.sol +++ b/src/lib/NonLinearDutchOrderLib.sol @@ -35,9 +35,11 @@ struct NonLinearDutchOrder { } /// @dev The changes in tokens (positive or negative) to subtract from the start amount -/// @dev The relativeBlock should be strictly increasing +/// @dev The relativeBlocks should be strictly increasing struct NonLinearDecay { - uint256[] relativeBlock; + // 16 uint16 values packed + // Can represent curves with points 2^16 blocks into the future + uint256 relativeBlocks; int256[] relativeAmount; } @@ -86,7 +88,7 @@ library NonLinearDutchOrderLib { bytes32 internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH = keccak256(NON_LINEAR_DUTCH_OUTPUT_TYPE); bytes internal constant NON_LINEAR_DECAY_TYPE = abi.encodePacked( "NonLinearDecay(", - "uint256[] relativeBlock,", + "uint256 relativeBlocks,", "int256[] relativeAmount)" ); bytes32 internal constant NON_LINEAR_DECAY_TYPE_HASH = keccak256(NON_LINEAR_DECAY_TYPE); @@ -111,7 +113,7 @@ library NonLinearDutchOrderLib { return keccak256( abi.encode( NON_LINEAR_DECAY_TYPE_HASH, - keccak256(abi.encodePacked(curve.relativeBlock)), + curve.relativeBlocks, keccak256(abi.encodePacked(curve.relativeAmount)) ) ); diff --git a/src/lib/SafeMath.sol b/src/lib/SafeMath.sol deleted file mode 100644 index 67228ec0..00000000 --- a/src/lib/SafeMath.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity ^0.8.0; - -library SafeMath { - function subIntFromUint(int256 a, uint256 b) public pure returns (uint256) { - if (a < 0) { - // If a is negative, add its absolute value to b - return b + uint256(-a); - } else { - // If a is positive, subtract it from b - require(b >= uint256(a), "negative_uint"); - return b - uint256(a); - } - } -} diff --git a/src/lib/Util.sol b/src/lib/Util.sol new file mode 100644 index 00000000..f3304641 --- /dev/null +++ b/src/lib/Util.sol @@ -0,0 +1,34 @@ +pragma solidity ^0.8.0; + +library Util { + function subIntFromUint(int256 a, uint256 b) public pure returns (uint256) { + if (a < 0) { + // If a is negative, add its absolute value to b + return b + uint256(-a); + } else { + // If a is positive, subtract it from b + require(b >= uint256(a), "negative_uint"); + return b - uint256(a); + } + } + + // Retrieve the nth uint16 value from a packed uint256 + function getUint16FromPacked(uint256 packedData, uint256 n) public pure returns (uint16) { + require(n < 16, "Index out of bounds"); + uint256 shiftAmount = n * 16; + uint16 result = uint16((packedData >> shiftAmount) & 0xFFFF); + return result; + } + + // Helper for creating a packed uint256 from a uint16 array + function packUint16Array(uint16[] memory inputArray) public pure returns (uint256) { + require(inputArray.length <= 16, "Array too long"); + uint256 packedData = 0; + + for (uint256 i = 0; i < inputArray.length; i++) { + packedData |= uint256(inputArray[i]) << (i * 16); + } + + return packedData; + } +} diff --git a/src/reactors/NonLinearDutchOrderReactor.sol b/src/reactors/NonLinearDutchOrderReactor.sol index b04452c0..f36edc71 100644 --- a/src/reactors/NonLinearDutchOrderReactor.sol +++ b/src/reactors/NonLinearDutchOrderReactor.sol @@ -28,8 +28,8 @@ contract NonLinearDutchOrderReactor is BaseReactor { /// @notice thrown when the decay curve is missing error MissingDecayCurve(); - /// @notice thrown when an order's deadline is before its end block - error DeadlineBeforeEndBlock(); + /// @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(); @@ -54,6 +54,9 @@ contract NonLinearDutchOrderReactor is BaseReactor { // hash the order _before_ overriding amounts, as this is the hash the user would have signed bytes32 orderHash = order.hash(); + if (order.info.deadline < block.timestamp) { + revert DeadlineReached(); + } _validateOrder(orderHash, order); _updateWithCosignerAmounts(order); @@ -109,26 +112,16 @@ contract NonLinearDutchOrderReactor is BaseReactor { /// @notice validate the dutch order fields /// - decay curves are defined - /// - deadline must be greater than or equal to decayEndBlock + /// - deadline must have not passed /// @dev Throws if the order is invalid function _validateOrder(bytes32 orderHash, NonLinearDutchOrder memory order) internal pure { - if (order.baseInput.curve.relativeBlock.length == 0) { + if (order.baseInput.curve.relativeAmount.length == 0) { revert MissingDecayCurve(); } for (uint256 i = 0; i < order.baseOutputs.length; i++) { - if (order.baseOutputs[i].curve.relativeBlock.length == 0) { + if (order.baseOutputs[i].curve.relativeAmount.length == 0) { revert MissingDecayCurve(); } - uint256 lastDecayPos = order.baseOutputs[i].curve.relativeBlock.length-1; - uint256 relativeOutputDecayEndBlock = order.baseOutputs[i].curve.relativeBlock[lastDecayPos]; - if (order.info.deadline < order.cosignerData.decayStartBlock + relativeOutputDecayEndBlock) { - revert DeadlineBeforeEndBlock(); - } - } - uint256 lastInputDecayPos = order.baseInput.curve.relativeBlock.length-1; - uint256 relativeDecayEndBlock = order.baseInput.curve.relativeBlock[lastInputDecayPos]; - if (order.info.deadline < order.cosignerData.decayStartBlock + relativeDecayEndBlock) { - revert DeadlineBeforeEndBlock(); } (bytes32 r, bytes32 s) = abi.decode(order.cosignature, (bytes32, bytes32)); diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol new file mode 100644 index 00000000..b5e95c66 --- /dev/null +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -0,0 +1,160 @@ +// 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 {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; +import {Util} from "../../src/lib/Util.sol"; +import {ArrayBuilder} from "../util/ArrayBuilder.sol"; + +contract NonLinearDutchDecayLibTest is Test { + function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 1)), + relativeAmount: ArrayBuilder.fillInt(1, 0) + }); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + } + + function testDutchDecayNoDecayYet() public { + uint256 decayStartBlock = 200; + uint256 startAmount = 1 ether; + int256 decayAmount = -1 ether; + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 1)), + relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + }); + vm.roll(100); + // at decayStartBlock + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + + vm.roll(80); + // before decayStartBlock + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + } + + function testDutchDecayNoDecayYetNegative() public { + uint256 decayStartBlock = 200; + uint256 startAmount = 1 ether; + int256 decayAmount = 1 ether; + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 1)), + relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + }); + vm.roll(100); + // at decayStartBlock + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + + vm.roll(80); + // before decayStartBlock + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + } + + function testDutchDecay() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + int256 decayAmount = -1 ether; + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + }); + vm.roll(150); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); + + vm.roll(180); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); + + vm.roll(110); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + + vm.roll(190); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + } + + function testDutchDecayNegative() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 2 ether; + int256 decayAmount = 1 ether; + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + }); + vm.roll(150); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); + + vm.roll(180); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.2 ether); + + vm.roll(110); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + + vm.roll(190); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + } + + function testDutchDecayFullyDecayed() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + int256 decayAmount = -1 ether; + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + }); + vm.roll(200); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); + + vm.warp(250); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); + } + + function testDutchDecayFullyDecayedNegative() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 2 ether; + int256 decayAmount = 1 ether; + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + }); + vm.roll(200); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + + vm.warp(250); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + } + + function testDutchDecayBounded(uint256 startAmount, uint256 decayAmount, uint256 decayStartBlock, uint16 decayDuration) + public + { + vm.assume(decayAmount > 0); + vm.assume(decayAmount < (UINT256_MAX / 2)); + vm.assume(startAmount <= UINT256_MAX - decayAmount); + vm.assume(decayDuration > 0); + + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, decayDuration)), + relativeAmount: ArrayBuilder.fillInt(1, 0-int256(decayAmount)) + }); + uint256 decayed = NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + assertGe(decayed, startAmount); + assertLe(decayed, startAmount + decayAmount); + } + + function testDutchDecayNegative(uint256 startAmount, uint256 decayAmount, uint256 decayStartBlock, uint16 decayDuration) + public + { + vm.assume(decayAmount > 0); + vm.assume(decayAmount < (UINT256_MAX / 2)); + // can't have neg prices + vm.assume(startAmount >= decayAmount); + vm.assume(startAmount <= UINT256_MAX - decayAmount); + vm.assume(decayDuration > 0); + + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, decayDuration)), + relativeAmount: ArrayBuilder.fillInt(1, int256(decayAmount)) + }); + uint256 decayed = NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + assertLe(decayed, startAmount); + assertGe(decayed, startAmount - decayAmount); + } +} diff --git a/test/util/ArrayBuilder.sol b/test/util/ArrayBuilder.sol index 16b095fd..58ffcf2e 100644 --- a/test/util/ArrayBuilder.sol +++ b/test/util/ArrayBuilder.sol @@ -38,6 +38,16 @@ library ArrayBuilder { } } + /// @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 From 01a6e3725987ece5a37be0c709168af6354c4fed Mon Sep 17 00:00:00 2001 From: Cody Born Date: Fri, 23 Aug 2024 14:42:00 -0600 Subject: [PATCH 05/24] More tests --- src/lib/NonLinearDutchDecayLib.sol | 2 +- src/lib/Util.sol | 17 +++- src/reactors/NonLinearDutchOrderReactor.sol | 11 +-- test/lib/NonLinearDutchDecayLib.t.sol | 94 ++++++++++++++++++++- test/lib/Util.t.sol | 48 +++++++++++ 5 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 test/lib/Util.t.sol diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/NonLinearDutchDecayLib.sol index f379ecd8..8551aabc 100644 --- a/src/lib/NonLinearDutchDecayLib.sol +++ b/src/lib/NonLinearDutchDecayLib.sol @@ -23,7 +23,7 @@ library NonLinearDutchDecayLib { uint256 blockDelta = block.number - decayStartBlock; // iterate through the points and locate the current segment for (uint256 i = 0; i < curve.relativeAmount.length; i++) { - // relativeBlocks is and array of uint16 packed one uint256 + // relativeBlocks is an array of uint16 packed one uint256 uint16 relativeBlock = Util.getUint16FromPacked(curve.relativeBlocks, i); if (relativeBlock >= blockDelta) { uint256 lastAmount = startAmount; diff --git a/src/lib/Util.sol b/src/lib/Util.sol index f3304641..dfb09179 100644 --- a/src/lib/Util.sol +++ b/src/lib/Util.sol @@ -1,20 +1,29 @@ pragma solidity ^0.8.0; library Util { + error NegativeUint(); + error IndexOutOfBounds(); + error InvalidArrLength(); + function subIntFromUint(int256 a, uint256 b) public pure returns (uint256) { if (a < 0) { // If a is negative, add its absolute value to b return b + uint256(-a); } else { // If a is positive, subtract it from b - require(b >= uint256(a), "negative_uint"); + if(b < uint256(a)) { + revert NegativeUint(); + } + return b - uint256(a); } } // Retrieve the nth uint16 value from a packed uint256 function getUint16FromPacked(uint256 packedData, uint256 n) public pure returns (uint16) { - require(n < 16, "Index out of bounds"); + if(n >= 16) { + revert IndexOutOfBounds(); + } uint256 shiftAmount = n * 16; uint16 result = uint16((packedData >> shiftAmount) & 0xFFFF); return result; @@ -22,7 +31,9 @@ library Util { // Helper for creating a packed uint256 from a uint16 array function packUint16Array(uint16[] memory inputArray) public pure returns (uint256) { - require(inputArray.length <= 16, "Array too long"); + if(inputArray.length > 16) { + revert InvalidArrLength(); + } uint256 packedData = 0; for (uint256 i = 0; i < inputArray.length; i++) { diff --git a/src/reactors/NonLinearDutchOrderReactor.sol b/src/reactors/NonLinearDutchOrderReactor.sol index f36edc71..8069e194 100644 --- a/src/reactors/NonLinearDutchOrderReactor.sol +++ b/src/reactors/NonLinearDutchOrderReactor.sol @@ -25,8 +25,8 @@ contract NonLinearDutchOrderReactor is BaseReactor { using NonLinearDutchDecayLib for NonLinearDutchInput; using ExclusivityLib for ResolvedOrder; - /// @notice thrown when the decay curve is missing - error MissingDecayCurve(); + /// @notice thrown when the decay curve is invalid + error InvalidDecayCurve(); /// @notice thrown when an order's deadline is passed error DeadlineReached(); @@ -115,12 +115,13 @@ contract NonLinearDutchOrderReactor is BaseReactor { /// - deadline must have not passed /// @dev Throws if the order is invalid function _validateOrder(bytes32 orderHash, NonLinearDutchOrder memory order) internal pure { - if (order.baseInput.curve.relativeAmount.length == 0) { - revert MissingDecayCurve(); + if (order.baseInput.curve.relativeAmount.length == 0 || + order.baseInput.curve.relativeAmount.length > 16) { + revert InvalidDecayCurve(); } for (uint256 i = 0; i < order.baseOutputs.length; i++) { if (order.baseOutputs[i].curve.relativeAmount.length == 0) { - revert MissingDecayCurve(); + revert InvalidDecayCurve(); } } diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index b5e95c66..3a3f61c0 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -126,7 +126,7 @@ contract NonLinearDutchDecayLibTest is Test { public { vm.assume(decayAmount > 0); - vm.assume(decayAmount < (UINT256_MAX / 2)); + vm.assume(decayAmount < 2**255-1); vm.assume(startAmount <= UINT256_MAX - decayAmount); vm.assume(decayDuration > 0); @@ -143,7 +143,7 @@ contract NonLinearDutchDecayLibTest is Test { public { vm.assume(decayAmount > 0); - vm.assume(decayAmount < (UINT256_MAX / 2)); + vm.assume(decayAmount < 2**255-1); // can't have neg prices vm.assume(startAmount >= decayAmount); vm.assume(startAmount <= UINT256_MAX - decayAmount); @@ -157,4 +157,94 @@ contract NonLinearDutchDecayLibTest is Test { assertLe(decayed, startAmount); assertGe(decayed, startAmount - decayAmount); } + + 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 + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(blocks), + relativeAmount: decayAmounts + }); + vm.roll(50); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + + vm.roll(150); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); + + vm.roll(200); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); + + vm.roll(210); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + + vm.roll(290); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + + vm.roll(300); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + + vm.roll(350); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), .5 ether); + + vm.roll(400); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); + + vm.roll(500); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); + } + + /* 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 + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(blocks), + relativeAmount: decayAmounts + }); + vm.roll(350); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), .25 ether); + } + + // function testDutchDecayToNegative() public { + // uint256 decayStartBlock = 100; + // uint256 startAmount = 1 ether; + // int256 decayAmount = 2 ether; + // NonLinearDecay memory curve = NonLinearDecay({ + // relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + // relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + // }); + // vm.roll(150); + // //vm.expectRevert(); + // NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + // } + + // function testDutchOverflowDecay() public { + // uint256 decayStartBlock = 100; + // uint256 startAmount = 1 ether; + // int256 decayAmount = 2**255-1; + // NonLinearDecay memory curve = NonLinearDecay({ + // relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + // relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + // }); + // vm.roll(150); + // //vm.expectRevert(); + // NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + // } } diff --git a/test/lib/Util.t.sol b/test/lib/Util.t.sol new file mode 100644 index 00000000..ec7c5921 --- /dev/null +++ b/test/lib/Util.t.sol @@ -0,0 +1,48 @@ +// 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 {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; +import {Util} from "../../src/lib/Util.sol"; +import {ArrayBuilder} from "../util/ArrayBuilder.sol"; + +contract UtilTest is Test { + + function testSubIntFromUint() public { + assertEq(Util.subIntFromUint(2, 2), 0); + assertEq(Util.subIntFromUint(1, 2), 1); + assertEq(Util.subIntFromUint(-1, 2), 3); + } + + function testSubNegIntFromUintRange(uint256 a, uint256 b) public { + vm.assume(a < 2**255-1); + vm.assume(b <= UINT256_MAX - a); + assertEq(Util.subIntFromUint(0-int256(a), b), b+a); + } + + function testSubIntFromUintRange(uint256 a, uint256 b) public { + vm.assume(a >= 0); + vm.assume(b >= a); + vm.assume(a < 2**255-1); + assertEq(Util.subIntFromUint(int256(a), b), b-a); + } + + function testSubIntFromUintNegativeUint() public { + vm.expectRevert(Util.NegativeUint.selector); + Util.subIntFromUint(2, 1); + } + + function testSubIntFromUintOverflow() public { + vm.expectRevert(); + Util.subIntFromUint(-1, UINT256_MAX); + } + + function testGetUint16FromPacked(uint16 value, uint256 length) public { + vm.assume(length <= 16); + uint256 packedArr = Util.packUint16Array(ArrayBuilder.fillUint16(length, value)); + for (uint256 i = 0; i < length; i++) { + assertEq(Util.getUint16FromPacked(packedArr, i), value); + } + } +} \ No newline at end of file From 669777f6f3f8b72825c47d2a2b254a0169fa0447 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Fri, 23 Aug 2024 15:22:05 -0600 Subject: [PATCH 06/24] Moar tests --- src/lib/NonLinearDutchDecayLib.sol | 2 +- test/lib/NonLinearDutchDecayLib.t.sol | 60 +++++++++++++++------------ test/lib/Util.t.sol | 11 +++++ 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/NonLinearDutchDecayLib.sol index 8551aabc..9e2357b6 100644 --- a/src/lib/NonLinearDutchDecayLib.sol +++ b/src/lib/NonLinearDutchDecayLib.sol @@ -10,7 +10,7 @@ import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; library NonLinearDutchDecayLib { using FixedPointMathLib for uint256; - /// @notice locates the surrounding points on the curve + /// @notice locates the current position on the curve and calculates the decay function decay(NonLinearDecay memory curve, uint256 startAmount, uint256 decayStartBlock) internal view diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index 3a3f61c0..4ecdbc27 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -7,7 +7,15 @@ import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../s import {Util} from "../../src/lib/Util.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; +/// @notice mock contract to test NonLinearDutchDecayLib functionality +contract MockNonLinearDutchDecayLibContract { + function decay(NonLinearDecay memory curve, uint256 startAmount, uint256 decayStartBlock) public view { + NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + } +} contract NonLinearDutchDecayLibTest is Test { + MockNonLinearDutchDecayLibContract mockNonLinearDutchDecayLibContract = new MockNonLinearDutchDecayLibContract(); + function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 1)), @@ -222,29 +230,29 @@ contract NonLinearDutchDecayLibTest is Test { assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), .25 ether); } - // function testDutchDecayToNegative() public { - // uint256 decayStartBlock = 100; - // uint256 startAmount = 1 ether; - // int256 decayAmount = 2 ether; - // NonLinearDecay memory curve = NonLinearDecay({ - // relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), - // relativeAmount: ArrayBuilder.fillInt(1, decayAmount) - // }); - // vm.roll(150); - // //vm.expectRevert(); - // NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); - // } - - // function testDutchOverflowDecay() public { - // uint256 decayStartBlock = 100; - // uint256 startAmount = 1 ether; - // int256 decayAmount = 2**255-1; - // NonLinearDecay memory curve = NonLinearDecay({ - // relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), - // relativeAmount: ArrayBuilder.fillInt(1, decayAmount) - // }); - // vm.roll(150); - // //vm.expectRevert(); - // NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); - // } -} + function testDutchDecayToNegative() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + int256 decayAmount = 2 ether; + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + }); + vm.roll(150); + vm.expectRevert(Util.NegativeUint.selector); + mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + } + + function testDutchOverflowDecay() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + int256 decayAmount = -(2**255-1); + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + }); + vm.roll(150); + vm.expectRevert(); + mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + } +} \ No newline at end of file diff --git a/test/lib/Util.t.sol b/test/lib/Util.t.sol index ec7c5921..3db74f24 100644 --- a/test/lib/Util.t.sol +++ b/test/lib/Util.t.sol @@ -45,4 +45,15 @@ contract UtilTest is Test { assertEq(Util.getUint16FromPacked(packedArr, i), value); } } + + function testPackUint16ArrayRevert() public { + vm.expectRevert(Util.InvalidArrLength.selector); + uint256 packedArr = Util.packUint16Array(ArrayBuilder.fillUint16(17, 1)); + } + + function testGetUint16FromPackedRevert() public { + uint256 packedArr = Util.packUint16Array(ArrayBuilder.fillUint16(5, 1)); + vm.expectRevert(Util.IndexOutOfBounds.selector); + Util.getUint16FromPacked(packedArr, 16); + } } \ No newline at end of file From 3d85925bef147bb4f8276cfdce70dffd8c72621e Mon Sep 17 00:00:00 2001 From: Cody Born Date: Fri, 23 Aug 2024 15:26:40 -0600 Subject: [PATCH 07/24] Remove yarn.lock --- yarn.lock | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 yarn.lock diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index fb57ccd1..00000000 --- a/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - From 8b934cd725132edd698e1f267a8f7bce23ebf2ba Mon Sep 17 00:00:00 2001 From: Cody Born Date: Fri, 23 Aug 2024 15:31:47 -0600 Subject: [PATCH 08/24] forge fmt --- src/lib/NonLinearDutchDecayLib.sol | 4 +- src/lib/NonLinearDutchOrderLib.sol | 34 +++++----------- src/lib/Util.sol | 8 ++-- src/reactors/NonLinearDutchOrderReactor.sol | 11 +++-- test/lib/NonLinearDutchDecayLib.t.sol | 45 +++++++++++---------- test/lib/Util.t.sol | 11 +++-- 6 files changed, 52 insertions(+), 61 deletions(-) diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/NonLinearDutchDecayLib.sol index 9e2357b6..31c64437 100644 --- a/src/lib/NonLinearDutchDecayLib.sol +++ b/src/lib/NonLinearDutchDecayLib.sol @@ -29,8 +29,8 @@ library NonLinearDutchDecayLib { uint256 lastAmount = startAmount; uint16 relativeStartBlock = 0; if (i != 0) { - lastAmount = Util.subIntFromUint(curve.relativeAmount[i-1], startAmount); - relativeStartBlock = Util.getUint16FromPacked(curve.relativeBlocks, i-1); + lastAmount = Util.subIntFromUint(curve.relativeAmount[i - 1], startAmount); + relativeStartBlock = Util.getUint16FromPacked(curve.relativeBlocks, i - 1); } uint256 nextAmount = Util.subIntFromUint(curve.relativeAmount[i], startAmount); // linear interpolation between the two points diff --git a/src/lib/NonLinearDutchOrderLib.sol b/src/lib/NonLinearDutchOrderLib.sol index c3387190..40f63a26 100644 --- a/src/lib/NonLinearDutchOrderLib.sol +++ b/src/lib/NonLinearDutchOrderLib.sol @@ -79,22 +79,16 @@ library NonLinearDutchOrderLib { "NonLinearDutchOutput[] baseOutputs)" ); bytes internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE = abi.encodePacked( - "NonLinearDutchOutput(", - "address token,", - "uint256 startAmount,", - "NonLinearDecay curve,", - "address recipient)" + "NonLinearDutchOutput(", "address token,", "uint256 startAmount,", "NonLinearDecay curve,", "address recipient)" ); bytes32 internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH = keccak256(NON_LINEAR_DUTCH_OUTPUT_TYPE); - bytes internal constant NON_LINEAR_DECAY_TYPE = abi.encodePacked( - "NonLinearDecay(", - "uint256 relativeBlocks,", - "int256[] relativeAmount)" - ); + bytes internal constant NON_LINEAR_DECAY_TYPE = + abi.encodePacked("NonLinearDecay(", "uint256 relativeBlocks,", "int256[] relativeAmount)"); bytes32 internal constant NON_LINEAR_DECAY_TYPE_HASH = keccak256(NON_LINEAR_DECAY_TYPE); - bytes internal constant ORDER_TYPE = - abi.encodePacked(NON_LINEAR_DECAY_TYPE, NON_LINEAR_DUTCH_ORDER_TYPE, NON_LINEAR_DUTCH_OUTPUT_TYPE, OrderInfoLib.ORDER_INFO_TYPE); + bytes internal constant ORDER_TYPE = abi.encodePacked( + NON_LINEAR_DECAY_TYPE, NON_LINEAR_DUTCH_ORDER_TYPE, NON_LINEAR_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 @@ -112,9 +106,7 @@ library NonLinearDutchOrderLib { function hash(NonLinearDecay memory curve) internal pure returns (bytes32) { return keccak256( abi.encode( - NON_LINEAR_DECAY_TYPE_HASH, - curve.relativeBlocks, - keccak256(abi.encodePacked(curve.relativeAmount)) + NON_LINEAR_DECAY_TYPE_HASH, curve.relativeBlocks, keccak256(abi.encodePacked(curve.relativeAmount)) ) ); } @@ -125,11 +117,7 @@ library NonLinearDutchOrderLib { function hash(NonLinearDutchInput memory input) internal pure returns (bytes32) { return keccak256( abi.encode( - NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH, - input.token, - input.startAmount, - hash(input.curve), - input.maxAmount + NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH, input.token, input.startAmount, hash(input.curve), input.maxAmount ) ); } @@ -173,11 +161,7 @@ library NonLinearDutchOrderLib { function hash(NonLinearDutchOrder memory order) internal pure returns (bytes32) { return keccak256( abi.encode( - ORDER_TYPE_HASH, - order.info.hash(), - order.cosigner, - hash(order.baseInput), - hash(order.baseOutputs) + ORDER_TYPE_HASH, order.info.hash(), order.cosigner, hash(order.baseInput), hash(order.baseOutputs) ) ); } diff --git a/src/lib/Util.sol b/src/lib/Util.sol index dfb09179..6e21b8b7 100644 --- a/src/lib/Util.sol +++ b/src/lib/Util.sol @@ -11,17 +11,17 @@ library Util { return b + uint256(-a); } else { // If a is positive, subtract it from b - if(b < uint256(a)) { + if (b < uint256(a)) { revert NegativeUint(); } - + return b - uint256(a); } } // Retrieve the nth uint16 value from a packed uint256 function getUint16FromPacked(uint256 packedData, uint256 n) public pure returns (uint16) { - if(n >= 16) { + if (n >= 16) { revert IndexOutOfBounds(); } uint256 shiftAmount = n * 16; @@ -31,7 +31,7 @@ library Util { // Helper for creating a packed uint256 from a uint16 array function packUint16Array(uint16[] memory inputArray) public pure returns (uint256) { - if(inputArray.length > 16) { + if (inputArray.length > 16) { revert InvalidArrLength(); } uint256 packedData = 0; diff --git a/src/reactors/NonLinearDutchOrderReactor.sol b/src/reactors/NonLinearDutchOrderReactor.sol index 8069e194..e618c7fe 100644 --- a/src/reactors/NonLinearDutchOrderReactor.sol +++ b/src/reactors/NonLinearDutchOrderReactor.sol @@ -6,7 +6,13 @@ 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 {NonLinearDutchOrderLib, NonLinearDutchOrder, CosignerData, NonLinearDutchOutput, NonLinearDutchInput} from "../lib/NonLinearDutchOrderLib.sol"; +import { + NonLinearDutchOrderLib, + NonLinearDutchOrder, + CosignerData, + NonLinearDutchOutput, + NonLinearDutchInput +} from "../lib/NonLinearDutchOrderLib.sol"; import {SignedOrder, ResolvedOrder} from "../base/ReactorStructs.sol"; /// @notice Reactor for non-linear dutch orders @@ -115,8 +121,7 @@ contract NonLinearDutchOrderReactor is BaseReactor { /// - deadline must have not passed /// @dev Throws if the order is invalid function _validateOrder(bytes32 orderHash, NonLinearDutchOrder memory order) internal pure { - if (order.baseInput.curve.relativeAmount.length == 0 || - order.baseInput.curve.relativeAmount.length > 16) { + if (order.baseInput.curve.relativeAmount.length == 0 || order.baseInput.curve.relativeAmount.length > 16) { revert InvalidDecayCurve(); } for (uint256 i = 0; i < order.baseOutputs.length; i++) { diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index 4ecdbc27..b23a7cb9 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -13,6 +13,7 @@ contract MockNonLinearDutchDecayLibContract { NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); } } + contract NonLinearDutchDecayLibTest is Test { MockNonLinearDutchDecayLibContract mockNonLinearDutchDecayLibContract = new MockNonLinearDutchDecayLibContract(); @@ -130,28 +131,34 @@ contract NonLinearDutchDecayLibTest is Test { assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); } - function testDutchDecayBounded(uint256 startAmount, uint256 decayAmount, uint256 decayStartBlock, uint16 decayDuration) - public - { + function testDutchDecayBounded( + uint256 startAmount, + uint256 decayAmount, + uint256 decayStartBlock, + uint16 decayDuration + ) public { vm.assume(decayAmount > 0); - vm.assume(decayAmount < 2**255-1); + vm.assume(decayAmount < 2 ** 255 - 1); vm.assume(startAmount <= UINT256_MAX - decayAmount); vm.assume(decayDuration > 0); NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, decayDuration)), - relativeAmount: ArrayBuilder.fillInt(1, 0-int256(decayAmount)) + relativeAmount: ArrayBuilder.fillInt(1, 0 - int256(decayAmount)) }); uint256 decayed = NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); assertGe(decayed, startAmount); assertLe(decayed, startAmount + decayAmount); } - function testDutchDecayNegative(uint256 startAmount, uint256 decayAmount, uint256 decayStartBlock, uint16 decayDuration) - public - { + function testDutchDecayNegative( + uint256 startAmount, + uint256 decayAmount, + uint256 decayStartBlock, + uint16 decayDuration + ) public { vm.assume(decayAmount > 0); - vm.assume(decayAmount < 2**255-1); + vm.assume(decayAmount < 2 ** 255 - 1); // can't have neg prices vm.assume(startAmount >= decayAmount); vm.assume(startAmount <= UINT256_MAX - decayAmount); @@ -177,10 +184,8 @@ contract NonLinearDutchDecayLibTest is Test { decayAmounts[0] = -1 ether; // 2 ether decayAmounts[1] = 0 ether; // 1 ether decayAmounts[2] = 1 ether; // 0 ether - NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(blocks), - relativeAmount: decayAmounts - }); + NonLinearDecay memory curve = + NonLinearDecay({relativeBlocks: Util.packUint16Array(blocks), relativeAmount: decayAmounts}); vm.roll(50); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); @@ -200,7 +205,7 @@ contract NonLinearDutchDecayLibTest is Test { assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); vm.roll(350); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), .5 ether); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.5 ether); vm.roll(400); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); @@ -222,12 +227,10 @@ contract NonLinearDutchDecayLibTest is Test { decayAmounts[0] = -1 ether; // 2 ether decayAmounts[1] = 0 ether; // 1 ether decayAmounts[2] = 1 ether; // 0 ether - NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(blocks), - relativeAmount: decayAmounts - }); + NonLinearDecay memory curve = + NonLinearDecay({relativeBlocks: Util.packUint16Array(blocks), relativeAmount: decayAmounts}); vm.roll(350); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), .25 ether); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.25 ether); } function testDutchDecayToNegative() public { @@ -246,7 +249,7 @@ contract NonLinearDutchDecayLibTest is Test { function testDutchOverflowDecay() public { uint256 decayStartBlock = 100; uint256 startAmount = 1 ether; - int256 decayAmount = -(2**255-1); + int256 decayAmount = -(2 ** 255 - 1); NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), relativeAmount: ArrayBuilder.fillInt(1, decayAmount) @@ -255,4 +258,4 @@ contract NonLinearDutchDecayLibTest is Test { vm.expectRevert(); mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); } -} \ No newline at end of file +} diff --git a/test/lib/Util.t.sol b/test/lib/Util.t.sol index 3db74f24..d70e67d3 100644 --- a/test/lib/Util.t.sol +++ b/test/lib/Util.t.sol @@ -8,7 +8,6 @@ import {Util} from "../../src/lib/Util.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; contract UtilTest is Test { - function testSubIntFromUint() public { assertEq(Util.subIntFromUint(2, 2), 0); assertEq(Util.subIntFromUint(1, 2), 1); @@ -16,16 +15,16 @@ contract UtilTest is Test { } function testSubNegIntFromUintRange(uint256 a, uint256 b) public { - vm.assume(a < 2**255-1); + vm.assume(a < 2 ** 255 - 1); vm.assume(b <= UINT256_MAX - a); - assertEq(Util.subIntFromUint(0-int256(a), b), b+a); + assertEq(Util.subIntFromUint(0 - int256(a), b), b + a); } function testSubIntFromUintRange(uint256 a, uint256 b) public { vm.assume(a >= 0); vm.assume(b >= a); - vm.assume(a < 2**255-1); - assertEq(Util.subIntFromUint(int256(a), b), b-a); + vm.assume(a < 2 ** 255 - 1); + assertEq(Util.subIntFromUint(int256(a), b), b - a); } function testSubIntFromUintNegativeUint() public { @@ -56,4 +55,4 @@ contract UtilTest is Test { vm.expectRevert(Util.IndexOutOfBounds.selector); Util.getUint16FromPacked(packedArr, 16); } -} \ No newline at end of file +} From b1945c8e4b5546e77518d37b8df6a3ca7c3c9016 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Mon, 26 Aug 2024 17:45:43 -0600 Subject: [PATCH 09/24] Custom types --- src/base/ReactorStructs.sol | 2 +- src/lib/ExclusivityLib.sol | 42 ++++- src/lib/MathExt.sol | 17 ++ src/lib/NonLinearDutchDecayLib.sol | 19 ++- src/lib/Util.sol | 45 ----- src/reactors/ExclusiveDutchOrderReactor.sol | 4 +- src/reactors/NonLinearDutchOrderReactor.sol | 5 +- src/types/Uint16Array.sol | 44 +++++ test/lib/ExclusivityLib.t.sol | 174 ++++++++++++++++---- test/lib/MathExt.t.sol | 41 +++++ test/lib/NonLinearDutchDecayLib.t.sol | 31 ++-- test/lib/Uint16Array.t.sol | 30 ++++ test/lib/Util.t.sol | 58 ------- test/util/mock/MockExclusivityLib.sol | 24 ++- 14 files changed, 362 insertions(+), 174 deletions(-) create mode 100644 src/lib/MathExt.sol delete mode 100644 src/lib/Util.sol create mode 100644 src/types/Uint16Array.sol create mode 100644 test/lib/MathExt.t.sol create mode 100644 test/lib/Uint16Array.t.sol delete mode 100644 test/lib/Util.t.sol diff --git a/src/base/ReactorStructs.sol b/src/base/ReactorStructs.sol index ba17ff09..2ab04a4c 100644 --- a/src/base/ReactorStructs.sol +++ b/src/base/ReactorStructs.sol @@ -17,7 +17,7 @@ struct OrderInfo { address swapper; // The nonce of the order, allowing for signature replay protection and cancellation uint256 nonce; - // The timestamp/blocknumber after which this order is no longer valid + // The timestamp after which this order is no longer valid uint256 deadline; // Custom validation contract IValidationCallback additionalValidationContract; diff --git a/src/lib/ExclusivityLib.sol b/src/lib/ExclusivityLib.sol index ca35d222..bd1e4a62 100644 --- a/src/lib/ExclusivityLib.sol +++ b/src/lib/ExclusivityLib.sol @@ -22,15 +22,44 @@ library ExclusivityLib { /// @param exclusive The exclusive address /// @param exclusivityEnd The exclusivity end time/block /// @param exclusivityOverrideBps The exclusivity override BPS - function handleExclusiveOverride( + function handleExclusiveOverrideTimestamp( + ResolvedOrder memory order, + address exclusive, + 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 time/block + /// @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 time/block + /// @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, - bool timeBased + uint256 currentPosition ) internal view { // if the filler has fill right, we proceed with the order as-is - if (hasFillingRights(exclusive, exclusivityEnd, timeBased)) { + if (hasFillingRights(exclusive, exclusivityEnd, currentPosition)) { return; } @@ -55,10 +84,7 @@ library ExclusivityLib { /// @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 exclusivityEnd, bool timeBased) internal view returns (bool) { - return exclusive == address(0) || - (timeBased && block.timestamp > exclusivityEnd) || - (!timeBased && block.number > exclusivityEnd) || - 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..bb3615aa --- /dev/null +++ b/src/lib/MathExt.sol @@ -0,0 +1,17 @@ +pragma solidity ^0.8.0; + +error NegativeUint(); + +function sub(uint256 a, int256 b) pure returns (uint256) { + if (b < 0) { + // If b is negative, add its absolute value to a + return a + uint256(-b); + } else { + // If b is positive, subtract it from a + if (a < uint256(b)) { + revert NegativeUint(); + } + + return a - uint256(b); + } +} diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/NonLinearDutchDecayLib.sol index 31c64437..f7f1bf25 100644 --- a/src/lib/NonLinearDutchDecayLib.sol +++ b/src/lib/NonLinearDutchDecayLib.sol @@ -3,12 +3,14 @@ pragma solidity ^0.8.0; import {OutputToken, InputToken} from "../base/ReactorStructs.sol"; import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../lib/NonLinearDutchOrderLib.sol"; -import {Util} from "./Util.sol"; import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; +import {sub} from "./MathExt.sol"; +import {Uint16Array, fromUnderlying} from "../types/Uint16Array.sol"; -/// @notice helpers for handling dutch order objects +/// @notice helpers for handling non-linear dutch order objects library NonLinearDutchDecayLib { using FixedPointMathLib for uint256; + using {sub} for uint256; /// @notice locates the current position on the curve and calculates the decay function decay(NonLinearDecay memory curve, uint256 startAmount, uint256 decayStartBlock) @@ -22,17 +24,18 @@ library NonLinearDutchDecayLib { } uint256 blockDelta = block.number - decayStartBlock; // iterate through the points and locate the current segment - for (uint256 i = 0; i < curve.relativeAmount.length; i++) { + for (uint16 i = 0; i < curve.relativeAmount.length; i++) { // relativeBlocks is an array of uint16 packed one uint256 - uint16 relativeBlock = Util.getUint16FromPacked(curve.relativeBlocks, i); + Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); + uint16 relativeBlock = relativeBlocks.getElement(i); if (relativeBlock >= blockDelta) { uint256 lastAmount = startAmount; uint16 relativeStartBlock = 0; if (i != 0) { - lastAmount = Util.subIntFromUint(curve.relativeAmount[i - 1], startAmount); - relativeStartBlock = Util.getUint16FromPacked(curve.relativeBlocks, i - 1); + lastAmount = startAmount.sub(curve.relativeAmount[i - 1]); + relativeStartBlock = relativeBlocks.getElement(i - 1); } - uint256 nextAmount = Util.subIntFromUint(curve.relativeAmount[i], startAmount); + uint256 nextAmount = startAmount.sub(curve.relativeAmount[i]); // linear interpolation between the two points unchecked { uint256 elapsed = blockDelta - relativeStartBlock; @@ -46,7 +49,7 @@ library NonLinearDutchDecayLib { } } // handle current block after last decay block - decayedAmount = Util.subIntFromUint(curve.relativeAmount[curve.relativeAmount.length - 1], startAmount); + decayedAmount = startAmount.sub(curve.relativeAmount[curve.relativeAmount.length - 1]); } /// @notice returns a decayed output using the given dutch spec and times diff --git a/src/lib/Util.sol b/src/lib/Util.sol deleted file mode 100644 index 6e21b8b7..00000000 --- a/src/lib/Util.sol +++ /dev/null @@ -1,45 +0,0 @@ -pragma solidity ^0.8.0; - -library Util { - error NegativeUint(); - error IndexOutOfBounds(); - error InvalidArrLength(); - - function subIntFromUint(int256 a, uint256 b) public pure returns (uint256) { - if (a < 0) { - // If a is negative, add its absolute value to b - return b + uint256(-a); - } else { - // If a is positive, subtract it from b - if (b < uint256(a)) { - revert NegativeUint(); - } - - return b - uint256(a); - } - } - - // Retrieve the nth uint16 value from a packed uint256 - function getUint16FromPacked(uint256 packedData, uint256 n) public pure returns (uint16) { - if (n >= 16) { - revert IndexOutOfBounds(); - } - uint256 shiftAmount = n * 16; - uint16 result = uint16((packedData >> shiftAmount) & 0xFFFF); - return result; - } - - // Helper for creating a packed uint256 from a uint16 array - function packUint16Array(uint16[] memory inputArray) public pure returns (uint256) { - if (inputArray.length > 16) { - revert InvalidArrLength(); - } - uint256 packedData = 0; - - for (uint256 i = 0; i < inputArray.length; i++) { - packedData |= uint256(inputArray[i]) << (i * 16); - } - - return packedData; - } -} diff --git a/src/reactors/ExclusiveDutchOrderReactor.sol b/src/reactors/ExclusiveDutchOrderReactor.sol index b130ac26..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, true); + resolvedOrder.handleExclusiveOverrideTimestamp( + order.exclusiveFiller, order.decayStartTime, order.exclusivityOverrideBps + ); } /// @inheritdoc BaseReactor diff --git a/src/reactors/NonLinearDutchOrderReactor.sol b/src/reactors/NonLinearDutchOrderReactor.sol index e618c7fe..eb86a64d 100644 --- a/src/reactors/NonLinearDutchOrderReactor.sol +++ b/src/reactors/NonLinearDutchOrderReactor.sol @@ -73,11 +73,10 @@ contract NonLinearDutchOrderReactor is BaseReactor { sig: signedOrder.sig, hash: orderHash }); - resolvedOrder.handleExclusiveOverride( + resolvedOrder.handleExclusiveOverrideBlock( order.cosignerData.exclusiveFiller, order.cosignerData.decayStartBlock, - order.cosignerData.exclusivityOverrideBps, - false + order.cosignerData.exclusivityOverrideBps ); } diff --git a/src/types/Uint16Array.sol b/src/types/Uint16Array.sol new file mode 100644 index 00000000..d871fccd --- /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; +using Uint16ArrayLibrary for Uint16Array global; +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; + } + } +} \ No newline at end of file diff --git a/test/lib/ExclusivityLib.t.sol b/test/lib/ExclusivityLib.t.sol index 20399c07..6d96f8f0 100644 --- a/test/lib/ExclusivityLib.t.sol +++ b/test/lib/ExclusivityLib.t.sol @@ -25,61 +25,82 @@ 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), true); + assertEq(exclusivity.hasFillingRights(exclusive, block.timestamp + 1, block.timestamp), true); } - function testExclusivityFail(address caller, address exclusive, uint256 nowTime, uint256 exclusiveTimestamp) + function testExclusivityFail(address caller, address exclusive, uint256 nowTime, uint256 exclusiveEnd) public { - vm.assume(nowTime <= exclusiveTimestamp); + vm.assume(nowTime <= exclusiveEnd); vm.assume(caller != exclusive); vm.assume(exclusive != address(0)); - vm.warp(nowTime); vm.prank(caller); - assertEq(exclusivity.hasFillingRights(exclusive, exclusiveTimestamp, true), 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), 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), 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, true); + exclusivity.handleExclusiveOverrideTimestamp(order, exclusive, 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 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 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, true); + 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,47 +113,100 @@ 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, true); + 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, true); + 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, true); + 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.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 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.handleExclusiveOverride(order, address(1), block.timestamp + 1, overrideAmt, true); + 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 testHandleExclusiveOverrideApplied(address caller, address exclusive, uint256 overrideAmt, uint128 amount) + function testHandleExclusiveOverrideTimestampApplied(address caller, address exclusive, uint256 overrideAmt, uint128 amount) public { vm.assume(caller != exclusive); @@ -142,13 +216,55 @@ 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, true); + 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 +282,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, true); + 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..702222ed --- /dev/null +++ b/test/lib/MathExt.t.sol @@ -0,0 +1,41 @@ +// 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 {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; +import {sub, NegativeUint} from "../../src/lib/MathExt.sol"; +import {ArrayBuilder} from "../util/ArrayBuilder.sol"; + +contract MathExtTest is Test { + using {sub} for uint256; + + function testSubIntFromUint() public { + assertEq(uint256(2).sub(2), 0); + assertEq(uint256(2).sub(1), 1); + assertEq(uint256(2).sub(-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(NegativeUint.selector); + uint256(1).sub(2); + } + + function testSubIntFromUintOverflow() public { + vm.expectRevert(); + UINT256_MAX.sub(-1); + } +} diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index b23a7cb9..e8d7b5c1 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -4,8 +4,9 @@ pragma solidity ^0.8.0; import {Test} from "forge-std/Test.sol"; import {NonLinearDutchDecayLib} from "../../src/lib/NonLinearDutchDecayLib.sol"; import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; -import {Util} from "../../src/lib/Util.sol"; +import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; +import {NegativeUint} from "../../src/lib/MathExt.sol"; /// @notice mock contract to test NonLinearDutchDecayLib functionality contract MockNonLinearDutchDecayLibContract { @@ -19,7 +20,7 @@ contract NonLinearDutchDecayLibTest is Test { function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 1)), + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), relativeAmount: ArrayBuilder.fillInt(1, 0) }); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); @@ -30,7 +31,7 @@ contract NonLinearDutchDecayLibTest is Test { uint256 startAmount = 1 ether; int256 decayAmount = -1 ether; NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 1)), + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), relativeAmount: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(100); @@ -47,7 +48,7 @@ contract NonLinearDutchDecayLibTest is Test { uint256 startAmount = 1 ether; int256 decayAmount = 1 ether; NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 1)), + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), relativeAmount: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(100); @@ -64,7 +65,7 @@ contract NonLinearDutchDecayLibTest is Test { uint256 startAmount = 1 ether; int256 decayAmount = -1 ether; NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmount: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(150); @@ -85,7 +86,7 @@ contract NonLinearDutchDecayLibTest is Test { uint256 startAmount = 2 ether; int256 decayAmount = 1 ether; NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmount: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(150); @@ -106,7 +107,7 @@ contract NonLinearDutchDecayLibTest is Test { uint256 startAmount = 1 ether; int256 decayAmount = -1 ether; NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmount: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(200); @@ -121,7 +122,7 @@ contract NonLinearDutchDecayLibTest is Test { uint256 startAmount = 2 ether; int256 decayAmount = 1 ether; NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmount: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(200); @@ -143,7 +144,7 @@ contract NonLinearDutchDecayLibTest is Test { vm.assume(decayDuration > 0); NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, decayDuration)), + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, decayDuration)), relativeAmount: ArrayBuilder.fillInt(1, 0 - int256(decayAmount)) }); uint256 decayed = NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); @@ -165,7 +166,7 @@ contract NonLinearDutchDecayLibTest is Test { vm.assume(decayDuration > 0); NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, decayDuration)), + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, decayDuration)), relativeAmount: ArrayBuilder.fillInt(1, int256(decayAmount)) }); uint256 decayed = NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); @@ -185,7 +186,7 @@ contract NonLinearDutchDecayLibTest is Test { decayAmounts[1] = 0 ether; // 1 ether decayAmounts[2] = 1 ether; // 0 ether NonLinearDecay memory curve = - NonLinearDecay({relativeBlocks: Util.packUint16Array(blocks), relativeAmount: decayAmounts}); + NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmount: decayAmounts}); vm.roll(50); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); @@ -228,7 +229,7 @@ contract NonLinearDutchDecayLibTest is Test { decayAmounts[1] = 0 ether; // 1 ether decayAmounts[2] = 1 ether; // 0 ether NonLinearDecay memory curve = - NonLinearDecay({relativeBlocks: Util.packUint16Array(blocks), relativeAmount: decayAmounts}); + NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmount: decayAmounts}); vm.roll(350); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.25 ether); } @@ -238,11 +239,11 @@ contract NonLinearDutchDecayLibTest is Test { uint256 startAmount = 1 ether; int256 decayAmount = 2 ether; NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmount: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(150); - vm.expectRevert(Util.NegativeUint.selector); + vm.expectRevert(NegativeUint.selector); mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); } @@ -251,7 +252,7 @@ contract NonLinearDutchDecayLibTest is Test { uint256 startAmount = 1 ether; int256 decayAmount = -(2 ** 255 - 1); NonLinearDecay memory curve = NonLinearDecay({ - relativeBlocks: Util.packUint16Array(ArrayBuilder.fillUint16(1, 100)), + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmount: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(150); diff --git a/test/lib/Uint16Array.t.sol b/test/lib/Uint16Array.t.sol new file mode 100644 index 00000000..2fb9d1c4 --- /dev/null +++ b/test/lib/Uint16Array.t.sol @@ -0,0 +1,30 @@ +// 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 {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; +import {ArrayBuilder} from "../util/ArrayBuilder.sol"; +import {Uint16Array, toUint16Array, InvalidArrLength, IndexOutOfBounds} from "../../src/types/Uint16Array.sol"; + +contract Uint16ArrayTest is Test { + + 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/lib/Util.t.sol b/test/lib/Util.t.sol deleted file mode 100644 index d70e67d3..00000000 --- a/test/lib/Util.t.sol +++ /dev/null @@ -1,58 +0,0 @@ -// 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 {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; -import {Util} from "../../src/lib/Util.sol"; -import {ArrayBuilder} from "../util/ArrayBuilder.sol"; - -contract UtilTest is Test { - function testSubIntFromUint() public { - assertEq(Util.subIntFromUint(2, 2), 0); - assertEq(Util.subIntFromUint(1, 2), 1); - assertEq(Util.subIntFromUint(-1, 2), 3); - } - - function testSubNegIntFromUintRange(uint256 a, uint256 b) public { - vm.assume(a < 2 ** 255 - 1); - vm.assume(b <= UINT256_MAX - a); - assertEq(Util.subIntFromUint(0 - int256(a), b), b + a); - } - - function testSubIntFromUintRange(uint256 a, uint256 b) public { - vm.assume(a >= 0); - vm.assume(b >= a); - vm.assume(a < 2 ** 255 - 1); - assertEq(Util.subIntFromUint(int256(a), b), b - a); - } - - function testSubIntFromUintNegativeUint() public { - vm.expectRevert(Util.NegativeUint.selector); - Util.subIntFromUint(2, 1); - } - - function testSubIntFromUintOverflow() public { - vm.expectRevert(); - Util.subIntFromUint(-1, UINT256_MAX); - } - - function testGetUint16FromPacked(uint16 value, uint256 length) public { - vm.assume(length <= 16); - uint256 packedArr = Util.packUint16Array(ArrayBuilder.fillUint16(length, value)); - for (uint256 i = 0; i < length; i++) { - assertEq(Util.getUint16FromPacked(packedArr, i), value); - } - } - - function testPackUint16ArrayRevert() public { - vm.expectRevert(Util.InvalidArrLength.selector); - uint256 packedArr = Util.packUint16Array(ArrayBuilder.fillUint16(17, 1)); - } - - function testGetUint16FromPackedRevert() public { - uint256 packedArr = Util.packUint16Array(ArrayBuilder.fillUint16(5, 1)); - vm.expectRevert(Util.IndexOutOfBounds.selector); - Util.getUint16FromPacked(packedArr, 16); - } -} diff --git a/test/util/mock/MockExclusivityLib.sol b/test/util/mock/MockExclusivityLib.sol index 264caa53..bb7252d6 100644 --- a/test/util/mock/MockExclusivityLib.sol +++ b/test/util/mock/MockExclusivityLib.sol @@ -5,18 +5,30 @@ 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 exclusivityEnd, - uint256 exclusivityOverrideBps, - bool timeBased + uint256 exclusivityOverrideBps ) external view returns (ResolvedOrder memory) { - ExclusivityLib.handleExclusiveOverride(order, exclusive, exclusivityEnd, exclusivityOverrideBps, timeBased); + ExclusivityLib.handleExclusiveOverrideTimestamp(order, exclusive, exclusivityEnd, exclusivityOverrideBps); + return order; + } + 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, bool timeBased) external view returns (bool pass) { - return ExclusivityLib.hasFillingRights(exclusive, exclusivityEnd, timeBased); + function hasFillingRights(address exclusive, uint256 exclusivityEnd, uint256 currentPosition) + external + view + returns (bool pass) + { + return ExclusivityLib.hasFillingRights(exclusive, exclusivityEnd, currentPosition); } } From 295575ff548bc743e5a982e1a0339b268f350920 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Mon, 26 Aug 2024 17:47:52 -0600 Subject: [PATCH 10/24] Missed file --- src/reactors/V2DutchOrderReactor.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/reactors/V2DutchOrderReactor.sol b/src/reactors/V2DutchOrderReactor.sol index 151017fe..a7a9e3a8 100644 --- a/src/reactors/V2DutchOrderReactor.sol +++ b/src/reactors/V2DutchOrderReactor.sol @@ -61,11 +61,10 @@ contract V2DutchOrderReactor is BaseReactor { sig: signedOrder.sig, hash: orderHash }); - resolvedOrder.handleExclusiveOverride( + resolvedOrder.handleExclusiveOverrideTimestamp( order.cosignerData.exclusiveFiller, order.cosignerData.decayStartTime, - order.cosignerData.exclusivityOverrideBps, - true + order.cosignerData.exclusivityOverrideBps ); } From a0a66d23a366d2f8fdbac067564e6558f4994bd1 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Tue, 27 Aug 2024 11:43:17 -0600 Subject: [PATCH 11/24] Improve readibility and add gas test --- src/lib/NonLinearDutchDecayLib.sol | 46 ++++++++++++++------- src/lib/NonLinearDutchOrderLib.sol | 6 +-- src/reactors/NonLinearDutchOrderReactor.sol | 6 +-- test/lib/NonLinearDutchDecayLib.t.sol | 46 +++++++++++++++------ 4 files changed, 70 insertions(+), 34 deletions(-) diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/NonLinearDutchDecayLib.sol index f7f1bf25..d6937dcb 100644 --- a/src/lib/NonLinearDutchDecayLib.sol +++ b/src/lib/NonLinearDutchDecayLib.sol @@ -22,9 +22,9 @@ library NonLinearDutchDecayLib { if (decayStartBlock >= block.number) { return startAmount; } - uint256 blockDelta = block.number - decayStartBlock; + uint16 blockDelta = uint16(block.number - decayStartBlock); // iterate through the points and locate the current segment - for (uint16 i = 0; i < curve.relativeAmount.length; i++) { + for (uint16 i = 0; i < curve.relativeAmounts.length; i++) { // relativeBlocks is an array of uint16 packed one uint256 Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); uint16 relativeBlock = relativeBlocks.getElement(i); @@ -32,26 +32,42 @@ library NonLinearDutchDecayLib { uint256 lastAmount = startAmount; uint16 relativeStartBlock = 0; if (i != 0) { - lastAmount = startAmount.sub(curve.relativeAmount[i - 1]); + lastAmount = startAmount.sub(curve.relativeAmounts[i - 1]); relativeStartBlock = relativeBlocks.getElement(i - 1); } - uint256 nextAmount = startAmount.sub(curve.relativeAmount[i]); - // linear interpolation between the two points - unchecked { - uint256 elapsed = blockDelta - relativeStartBlock; - uint256 duration = relativeBlock - relativeStartBlock; - if (nextAmount < lastAmount) { - return lastAmount - (lastAmount - nextAmount).mulDivDown(elapsed, duration); - } else { - return lastAmount + (nextAmount - lastAmount).mulDivUp(elapsed, duration); - } - } + uint256 nextAmount = startAmount.sub(curve.relativeAmounts[i]); + return linearDecay(relativeStartBlock, relativeBlock, blockDelta, lastAmount, nextAmount); } } // handle current block after last decay block - decayedAmount = startAmount.sub(curve.relativeAmount[curve.relativeAmount.length - 1]); + decayedAmount = startAmount.sub(curve.relativeAmounts[curve.relativeAmounts.length - 1]); } + /// @notice returns the linear interpolation between the two points + /// @param startBlock The start of the decay + /// @param endBlock The end of the decay + /// @param currentBlock 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( + uint16 startBlock, + uint16 endBlock, + uint16 currentBlock, + uint256 startAmount, + uint256 endAmount + ) internal pure returns (uint256) { + unchecked { + uint256 elapsed = currentBlock - startBlock; + uint256 duration = endBlock - startBlock; + if (endAmount < startAmount) { + return startAmount - (startAmount - endAmount).mulDivDown(elapsed, duration); + } else { + return startAmount + (endAmount - startAmount).mulDivUp(elapsed, duration); + } + } + } + + /// @notice returns a decayed output using the given dutch spec and times /// @param output The output to decay /// @param decayStartBlock The block to start decaying diff --git a/src/lib/NonLinearDutchOrderLib.sol b/src/lib/NonLinearDutchOrderLib.sol index 40f63a26..59abbc30 100644 --- a/src/lib/NonLinearDutchOrderLib.sol +++ b/src/lib/NonLinearDutchOrderLib.sol @@ -40,7 +40,7 @@ struct NonLinearDecay { // 16 uint16 values packed // Can represent curves with points 2^16 blocks into the future uint256 relativeBlocks; - int256[] relativeAmount; + int256[] relativeAmounts; } /// @dev An amount of input tokens that increases non-linearly over time @@ -83,7 +83,7 @@ library NonLinearDutchOrderLib { ); bytes32 internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH = keccak256(NON_LINEAR_DUTCH_OUTPUT_TYPE); bytes internal constant NON_LINEAR_DECAY_TYPE = - abi.encodePacked("NonLinearDecay(", "uint256 relativeBlocks,", "int256[] relativeAmount)"); + abi.encodePacked("NonLinearDecay(", "uint256 relativeBlocks,", "int256[] relativeAmounts)"); bytes32 internal constant NON_LINEAR_DECAY_TYPE_HASH = keccak256(NON_LINEAR_DECAY_TYPE); bytes internal constant ORDER_TYPE = abi.encodePacked( @@ -106,7 +106,7 @@ library NonLinearDutchOrderLib { function hash(NonLinearDecay memory curve) internal pure returns (bytes32) { return keccak256( abi.encode( - NON_LINEAR_DECAY_TYPE_HASH, curve.relativeBlocks, keccak256(abi.encodePacked(curve.relativeAmount)) + NON_LINEAR_DECAY_TYPE_HASH, curve.relativeBlocks, keccak256(abi.encodePacked(curve.relativeAmounts)) ) ); } diff --git a/src/reactors/NonLinearDutchOrderReactor.sol b/src/reactors/NonLinearDutchOrderReactor.sol index eb86a64d..456922c6 100644 --- a/src/reactors/NonLinearDutchOrderReactor.sol +++ b/src/reactors/NonLinearDutchOrderReactor.sol @@ -16,7 +16,7 @@ import { import {SignedOrder, ResolvedOrder} from "../base/ReactorStructs.sol"; /// @notice Reactor for non-linear dutch orders -/// @dev V2 orders must be cosigned by the specified cosigner to override timings and starting values +/// @dev Non-linear 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 @@ -120,11 +120,11 @@ contract NonLinearDutchOrderReactor is BaseReactor { /// - deadline must have not passed /// @dev Throws if the order is invalid function _validateOrder(bytes32 orderHash, NonLinearDutchOrder memory order) internal pure { - if (order.baseInput.curve.relativeAmount.length == 0 || order.baseInput.curve.relativeAmount.length > 16) { + if (order.baseInput.curve.relativeAmounts.length == 0 || order.baseInput.curve.relativeAmounts.length > 16) { revert InvalidDecayCurve(); } for (uint256 i = 0; i < order.baseOutputs.length; i++) { - if (order.baseOutputs[i].curve.relativeAmount.length == 0) { + if (order.baseOutputs[i].curve.relativeAmounts.length == 0) { revert InvalidDecayCurve(); } } diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index e8d7b5c1..2f89539f 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import {Test} from "forge-std/Test.sol"; +import {console} from "forge-std/console.sol"; import {NonLinearDutchDecayLib} from "../../src/lib/NonLinearDutchDecayLib.sol"; import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol"; @@ -21,7 +22,7 @@ contract NonLinearDutchDecayLibTest is Test { function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), - relativeAmount: ArrayBuilder.fillInt(1, 0) + relativeAmounts: ArrayBuilder.fillInt(1, 0) }); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); } @@ -32,7 +33,7 @@ contract NonLinearDutchDecayLibTest is Test { int256 decayAmount = -1 ether; NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), - relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(100); // at decayStartBlock @@ -49,7 +50,7 @@ contract NonLinearDutchDecayLibTest is Test { int256 decayAmount = 1 ether; NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), - relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(100); // at decayStartBlock @@ -66,7 +67,7 @@ contract NonLinearDutchDecayLibTest is Test { int256 decayAmount = -1 ether; NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(150); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); @@ -87,7 +88,7 @@ contract NonLinearDutchDecayLibTest is Test { int256 decayAmount = 1 ether; NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(150); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); @@ -108,7 +109,7 @@ contract NonLinearDutchDecayLibTest is Test { int256 decayAmount = -1 ether; NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(200); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); @@ -123,7 +124,7 @@ contract NonLinearDutchDecayLibTest is Test { int256 decayAmount = 1 ether; NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(200); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); @@ -145,7 +146,7 @@ contract NonLinearDutchDecayLibTest is Test { NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, decayDuration)), - relativeAmount: ArrayBuilder.fillInt(1, 0 - int256(decayAmount)) + relativeAmounts: ArrayBuilder.fillInt(1, 0 - int256(decayAmount)) }); uint256 decayed = NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); assertGe(decayed, startAmount); @@ -167,7 +168,7 @@ contract NonLinearDutchDecayLibTest is Test { NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, decayDuration)), - relativeAmount: ArrayBuilder.fillInt(1, int256(decayAmount)) + relativeAmounts: ArrayBuilder.fillInt(1, int256(decayAmount)) }); uint256 decayed = NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); assertLe(decayed, startAmount); @@ -186,7 +187,7 @@ contract NonLinearDutchDecayLibTest is Test { decayAmounts[1] = 0 ether; // 1 ether decayAmounts[2] = 1 ether; // 0 ether NonLinearDecay memory curve = - NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmount: decayAmounts}); + NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); vm.roll(50); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); @@ -215,6 +216,25 @@ contract NonLinearDutchDecayLibTest is Test { assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); } + function testMultiPointDutchDecayGas() 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 + NonLinearDecay memory curve = + NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); + + vm.roll(350); + NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + console.log("testMultiPointDutchDecayGas: ", vm.lastCallGas().gasTotalUsed); + } + /* Invalid order scenarios */ function testDutchDecayNonAscendingBlocks() public { @@ -229,7 +249,7 @@ contract NonLinearDutchDecayLibTest is Test { decayAmounts[1] = 0 ether; // 1 ether decayAmounts[2] = 1 ether; // 0 ether NonLinearDecay memory curve = - NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmount: decayAmounts}); + NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); vm.roll(350); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.25 ether); } @@ -240,7 +260,7 @@ contract NonLinearDutchDecayLibTest is Test { int256 decayAmount = 2 ether; NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(150); vm.expectRevert(NegativeUint.selector); @@ -253,7 +273,7 @@ contract NonLinearDutchDecayLibTest is Test { int256 decayAmount = -(2 ** 255 - 1); NonLinearDecay memory curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmount: ArrayBuilder.fillInt(1, decayAmount) + relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(150); vm.expectRevert(); From 59153044b910bb6d920f25cfc25d2469648421ad Mon Sep 17 00:00:00 2001 From: Cody Born Date: Wed, 28 Aug 2024 19:43:39 -0600 Subject: [PATCH 12/24] Binary search --- src/lib/NonLinearDutchDecayLib.sol | 84 ++++++++++++++++----- src/reactors/NonLinearDutchOrderReactor.sol | 13 ---- test/lib/NonLinearDutchDecayLib.t.sol | 74 +++++++++++++++++- 3 files changed, 140 insertions(+), 31 deletions(-) diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/NonLinearDutchDecayLib.sol index d6937dcb..e8f7ee54 100644 --- a/src/lib/NonLinearDutchDecayLib.sol +++ b/src/lib/NonLinearDutchDecayLib.sol @@ -7,40 +7,88 @@ import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; import {sub} from "./MathExt.sol"; import {Uint16Array, fromUnderlying} from "../types/Uint16Array.sol"; +/// @notice thrown when the decay curve is invalid +error InvalidDecayCurve(); + /// @notice helpers for handling non-linear dutch order objects library NonLinearDutchDecayLib { + using FixedPointMathLib for uint256; using {sub} for uint256; /// @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 function decay(NonLinearDecay memory curve, uint256 startAmount, uint256 decayStartBlock) 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) { + if (decayStartBlock >= block.number || curve.relativeAmounts.length == 0) { return startAmount; } + Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); uint16 blockDelta = uint16(block.number - decayStartBlock); - // iterate through the points and locate the current segment - for (uint16 i = 0; i < curve.relativeAmounts.length; i++) { - // relativeBlocks is an array of uint16 packed one uint256 - Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); - uint16 relativeBlock = relativeBlocks.getElement(i); - if (relativeBlock >= blockDelta) { - uint256 lastAmount = startAmount; - uint16 relativeStartBlock = 0; - if (i != 0) { - lastAmount = startAmount.sub(curve.relativeAmounts[i - 1]); - relativeStartBlock = relativeBlocks.getElement(i - 1); + // Special case for when we need to use the decayStartBlock (0) + if (relativeBlocks.getElement(0) > blockDelta) { + return linearDecay(0, relativeBlocks.getElement(0), blockDelta, startAmount, startAmount.sub(curve.relativeAmounts[0])); + } + // the current pos is within or after the curve + uint16 prev; + uint16 next; + (prev, next) = locateCurvePosition(curve, blockDelta); + uint256 lastAmount = startAmount.sub(curve.relativeAmounts[prev]); + uint256 nextAmount = startAmount.sub(curve.relativeAmounts[next]); + return linearDecay(relativeBlocks.getElement(prev), relativeBlocks.getElement(next), blockDelta, lastAmount, nextAmount); + } + + /// @notice Locates the current position on the curve using a binary search + /// @param curve The curve to search + /// @param currentRelativeBlock The current relative position + /// @return prev The relative block before the current position + /// @return next The relative block after the current position + function locateCurvePosition(NonLinearDecay memory curve, uint16 currentRelativeBlock) + internal + pure + returns (uint16 prev, uint16 next) + { + Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); + uint16 left = 0; + uint16 right = uint16(curve.relativeAmounts.length) - 1; + uint16 mid; + + while (left <= right) { + mid = left + (right - left) / 2; + uint16 midBlock = relativeBlocks.getElement(mid); + + if (midBlock == currentRelativeBlock) { + return (mid, mid); + } else if (midBlock > currentRelativeBlock) { + if (mid == 0) { + return (mid, mid); + } else if (relativeBlocks.getElement(mid - 1) < currentRelativeBlock) { + return (mid - 1, mid); + } else { + right = mid - 1; + } + } else { + if (mid == curve.relativeAmounts.length - 1) { + return (mid, mid); + } else if (relativeBlocks.getElement(mid + 1) > currentRelativeBlock) { + return (mid, mid + 1); + } else { + left = mid + 1; } - uint256 nextAmount = startAmount.sub(curve.relativeAmounts[i]); - return linearDecay(relativeStartBlock, relativeBlock, blockDelta, lastAmount, nextAmount); } } - // handle current block after last decay block - decayedAmount = startAmount.sub(curve.relativeAmounts[curve.relativeAmounts.length - 1]); + revert InvalidDecayCurve(); } /// @notice returns the linear interpolation between the two points @@ -56,6 +104,9 @@ library NonLinearDutchDecayLib { uint256 startAmount, uint256 endAmount ) internal pure returns (uint256) { + if (currentBlock >= endBlock) { + return endAmount; + } unchecked { uint256 elapsed = currentBlock - startBlock; uint256 duration = endBlock - startBlock; @@ -67,7 +118,6 @@ library NonLinearDutchDecayLib { } } - /// @notice returns a decayed output using the given dutch spec and times /// @param output The output to decay /// @param decayStartBlock The block to start decaying diff --git a/src/reactors/NonLinearDutchOrderReactor.sol b/src/reactors/NonLinearDutchOrderReactor.sol index 456922c6..0b02da29 100644 --- a/src/reactors/NonLinearDutchOrderReactor.sol +++ b/src/reactors/NonLinearDutchOrderReactor.sol @@ -31,9 +31,6 @@ contract NonLinearDutchOrderReactor is BaseReactor { using NonLinearDutchDecayLib for NonLinearDutchInput; using ExclusivityLib for ResolvedOrder; - /// @notice thrown when the decay curve is invalid - error InvalidDecayCurve(); - /// @notice thrown when an order's deadline is passed error DeadlineReached(); @@ -116,19 +113,9 @@ contract NonLinearDutchOrderReactor is BaseReactor { } /// @notice validate the dutch order fields - /// - decay curves are defined /// - deadline must have not passed /// @dev Throws if the order is invalid function _validateOrder(bytes32 orderHash, NonLinearDutchOrder memory order) internal pure { - if (order.baseInput.curve.relativeAmounts.length == 0 || order.baseInput.curve.relativeAmounts.length > 16) { - revert InvalidDecayCurve(); - } - for (uint256 i = 0; i < order.baseOutputs.length; i++) { - if (order.baseOutputs[i].curve.relativeAmounts.length == 0) { - revert InvalidDecayCurve(); - } - } - (bytes32 r, bytes32 s) = abi.decode(order.cosignature, (bytes32, bytes32)); uint8 v = uint8(order.cosignature[64]); // cosigner signs over (orderHash || cosignerData) diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index 2f89539f..83befc1e 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {Test} from "forge-std/Test.sol"; import {console} from "forge-std/console.sol"; -import {NonLinearDutchDecayLib} from "../../src/lib/NonLinearDutchDecayLib.sol"; +import {NonLinearDutchDecayLib, InvalidDecayCurve} from "../../src/lib/NonLinearDutchDecayLib.sol"; import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; @@ -19,8 +19,68 @@ contract MockNonLinearDutchDecayLibContract { contract NonLinearDutchDecayLibTest is Test { MockNonLinearDutchDecayLibContract mockNonLinearDutchDecayLibContract = new MockNonLinearDutchDecayLibContract(); + function testLocateCurvePositionSingle() public { + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), + relativeAmounts: ArrayBuilder.fillInt(1, 0) + }); + uint16 prev; + uint16 next; + (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 1); + assertEq(prev, 0); + assertEq(next, 0); + + (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 2); + assertEq(prev, 0); + assertEq(next, 0); + } + + + function testLocateCurvePositionMulti() public { + 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 + NonLinearDecay memory curve = + NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); + + uint16 prev; + uint16 next; + (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 150); + assertEq(prev, 0); + assertEq(next, 1); + + (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 200); + assertEq(prev, 1); + assertEq(next, 1); + + (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 250); + assertEq(prev, 1); + assertEq(next, 2); + + (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 300); + assertEq(prev, 2); + assertEq(next, 2); + + (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 350); + assertEq(prev, 2); + assertEq(next, 2); + } + function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { + // Empty curve NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: toUint256(ArrayBuilder.fillUint16(0, 0)), + relativeAmounts: ArrayBuilder.fillInt(0, 0) + }); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + + // Single value with 0 amount change + curve = NonLinearDecay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), relativeAmounts: ArrayBuilder.fillInt(1, 0) }); @@ -279,4 +339,16 @@ contract NonLinearDutchDecayLibTest is Test { vm.expectRevert(); mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); } + + function testDutchMismatchedDecay() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + NonLinearDecay memory curve = NonLinearDecay({ + relativeBlocks: toUint256(ArrayBuilder.fillUint16(16, 1)), + relativeAmounts: ArrayBuilder.fillInt(17, 0) + }); + vm.roll(150); + vm.expectRevert(InvalidDecayCurve.selector); + mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + } } From 075ebd852782f0560eb8d28af158454e472747af Mon Sep 17 00:00:00 2001 From: Cody Born Date: Thu, 29 Aug 2024 14:29:00 -0600 Subject: [PATCH 13/24] Linear search + gas tests --- ...veDutchOrder-BaseExecuteSingleWithFee.snap | 2 +- ...Base-ExclusiveDutchOrder-ExecuteBatch.snap | 2 +- ...utchOrder-ExecuteBatchMultipleOutputs.snap | 2 +- ...teBatchMultipleOutputsDifferentTokens.snap | 2 +- ...veDutchOrder-ExecuteBatchNativeOutput.snap | 2 +- ...ase-ExclusiveDutchOrder-ExecuteSingle.snap | 2 +- ...eDutchOrder-ExecuteSingleNativeOutput.snap | 2 +- ...iveDutchOrder-ExecuteSingleValidation.snap | 2 +- ...xclusiveDutchOrder-RevertInvalidNonce.snap | 2 +- ...V2DutchOrder-BaseExecuteSingleWithFee.snap | 2 +- .../Base-V2DutchOrder-ExclusiveFiller.snap | 2 +- .../Base-V2DutchOrder-ExecuteBatch.snap | 2 +- ...utchOrder-ExecuteBatchMultipleOutputs.snap | 2 +- ...teBatchMultipleOutputsDifferentTokens.snap | 2 +- ...V2DutchOrder-ExecuteBatchNativeOutput.snap | 2 +- .../Base-V2DutchOrder-ExecuteSingle.snap | 2 +- ...2DutchOrder-ExecuteSingleNativeOutput.snap | 2 +- ...-V2DutchOrder-ExecuteSingleValidation.snap | 2 +- .../Base-V2DutchOrder-InputOverride.snap | 2 +- .../Base-V2DutchOrder-OutputOverride.snap | 2 +- .../Base-V2DutchOrder-RevertInvalidNonce.snap | 2 +- .forge-snapshots/NonLinear-DutchDecay.snap | 1 + .../NonLinear-DutchDecayBounded.snap | 1 + .../NonLinear-DutchDecayFullyDecayed.snap | 1 + ...Linear-DutchDecayFullyDecayedNegative.snap | 1 + .../NonLinear-DutchDecayNegative.snap | 1 + .../NonLinear-DutchDecayNoDecay.snap | 1 + .../NonLinear-DutchDecayNoDecayYet.snap | 1 + ...onLinear-DutchDecayNoDecayYetNegative.snap | 1 + ...onLinear-ExtendedMultiPointDutchDecay.snap | 1 + .../NonLinear-LocateCurvePositionMulti.snap | 1 + .../NonLinear-LocateCurvePositionSingle.snap | 1 + .../NonLinear-MultiPointDutchDecay.snap | 1 + ...omparisonTest-InterfaceAndProtocolFee.snap | 2 +- ...Test-InterfaceAndProtocolFeeEthOutput.snap | 2 +- ...colFeesGasComparisonTest-InterfaceFee.snap | 2 +- ...sComparisonTest-InterfaceFeeEthOutput.snap | 2 +- .../ProtocolFeesGasComparisonTest-NoFees.snap | 2 +- ...FeesGasComparisonTest-NoFeesEthOutput.snap | 2 +- src/lib/NonLinearDutchDecayLib.sol | 35 +--- test/lib/NonLinearDutchDecayLib.t.sol | 161 ++++++++++++++++-- 41 files changed, 190 insertions(+), 72 deletions(-) create mode 100644 .forge-snapshots/NonLinear-DutchDecay.snap create mode 100644 .forge-snapshots/NonLinear-DutchDecayBounded.snap create mode 100644 .forge-snapshots/NonLinear-DutchDecayFullyDecayed.snap create mode 100644 .forge-snapshots/NonLinear-DutchDecayFullyDecayedNegative.snap create mode 100644 .forge-snapshots/NonLinear-DutchDecayNegative.snap create mode 100644 .forge-snapshots/NonLinear-DutchDecayNoDecay.snap create mode 100644 .forge-snapshots/NonLinear-DutchDecayNoDecayYet.snap create mode 100644 .forge-snapshots/NonLinear-DutchDecayNoDecayYetNegative.snap create mode 100644 .forge-snapshots/NonLinear-ExtendedMultiPointDutchDecay.snap create mode 100644 .forge-snapshots/NonLinear-LocateCurvePositionMulti.snap create mode 100644 .forge-snapshots/NonLinear-LocateCurvePositionSingle.snap create mode 100644 .forge-snapshots/NonLinear-MultiPointDutchDecay.snap diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap index be96dd61..33bf6499 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -181768 \ 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 10e3d075..9f08d23a 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -196789 \ 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 1b794b03..33f65067 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -206440 \ 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 a1dce779..51eeba6b 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -259996 \ 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 c58a3fd4..3ae6d7d9 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -190315 \ 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 2b30de5b..57918cda 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -148154 \ 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 cddd3bf4..e04bfbcd 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -133721 \ 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 3881d3b2..16e736c7 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -157469 \ 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 06c757f4..efa56cd6 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-RevertInvalidNonce.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-RevertInvalidNonce.snap @@ -1 +1 @@ -26713 \ 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 e4d22144..e10d096e 100644 --- a/.forge-snapshots/Base-V2DutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-V2DutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -188553 \ 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 8151ad14..3efb8afe 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExclusiveFiller.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExclusiveFiller.snap @@ -1 +1 @@ -158750 \ 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 a254d155..a36a72e8 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -210462 \ 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 5169eb84..5bd26769 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -220687 \ 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 5b99d0e0..a9b7a4b0 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -274814 \ 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 e3d89d6a..fcd794f1 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -203988 \ 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 6510c5fe..ddc8d048 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -154936 \ 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 5b9b837d..2ed6ee49 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -140498 \ 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 304b3f43..85b93caf 100644 --- a/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-V2DutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -164247 \ 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 bf78aba0..100cb528 100644 --- a/.forge-snapshots/Base-V2DutchOrder-InputOverride.snap +++ b/.forge-snapshots/Base-V2DutchOrder-InputOverride.snap @@ -1 +1 @@ -158778 \ 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 7796031e..446c64c6 100644 --- a/.forge-snapshots/Base-V2DutchOrder-OutputOverride.snap +++ b/.forge-snapshots/Base-V2DutchOrder-OutputOverride.snap @@ -1 +1 @@ -158727 \ 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 b4b34a35..b613387f 100644 --- a/.forge-snapshots/Base-V2DutchOrder-RevertInvalidNonce.snap +++ b/.forge-snapshots/Base-V2DutchOrder-RevertInvalidNonce.snap @@ -1 +1 @@ -33510 \ No newline at end of file +33536 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-DutchDecay.snap b/.forge-snapshots/NonLinear-DutchDecay.snap new file mode 100644 index 00000000..9227f3bd --- /dev/null +++ b/.forge-snapshots/NonLinear-DutchDecay.snap @@ -0,0 +1 @@ +17352 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-DutchDecayBounded.snap b/.forge-snapshots/NonLinear-DutchDecayBounded.snap new file mode 100644 index 00000000..3af7d09a --- /dev/null +++ b/.forge-snapshots/NonLinear-DutchDecayBounded.snap @@ -0,0 +1 @@ +1070 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-DutchDecayFullyDecayed.snap b/.forge-snapshots/NonLinear-DutchDecayFullyDecayed.snap new file mode 100644 index 00000000..a8966006 --- /dev/null +++ b/.forge-snapshots/NonLinear-DutchDecayFullyDecayed.snap @@ -0,0 +1 @@ +14834 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-DutchDecayFullyDecayedNegative.snap b/.forge-snapshots/NonLinear-DutchDecayFullyDecayedNegative.snap new file mode 100644 index 00000000..3dabfaf8 --- /dev/null +++ b/.forge-snapshots/NonLinear-DutchDecayFullyDecayedNegative.snap @@ -0,0 +1 @@ +14686 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-DutchDecayNegative.snap b/.forge-snapshots/NonLinear-DutchDecayNegative.snap new file mode 100644 index 00000000..3af7d09a --- /dev/null +++ b/.forge-snapshots/NonLinear-DutchDecayNegative.snap @@ -0,0 +1 @@ +1070 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-DutchDecayNoDecay.snap b/.forge-snapshots/NonLinear-DutchDecayNoDecay.snap new file mode 100644 index 00000000..2c6feee2 --- /dev/null +++ b/.forge-snapshots/NonLinear-DutchDecayNoDecay.snap @@ -0,0 +1 @@ +4721 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-DutchDecayNoDecayYet.snap b/.forge-snapshots/NonLinear-DutchDecayNoDecayYet.snap new file mode 100644 index 00000000..ccc50fd7 --- /dev/null +++ b/.forge-snapshots/NonLinear-DutchDecayNoDecayYet.snap @@ -0,0 +1 @@ +4281 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-DutchDecayNoDecayYetNegative.snap b/.forge-snapshots/NonLinear-DutchDecayNoDecayYetNegative.snap new file mode 100644 index 00000000..ccc50fd7 --- /dev/null +++ b/.forge-snapshots/NonLinear-DutchDecayNoDecayYetNegative.snap @@ -0,0 +1 @@ +4281 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-ExtendedMultiPointDutchDecay.snap b/.forge-snapshots/NonLinear-ExtendedMultiPointDutchDecay.snap new file mode 100644 index 00000000..5b397aee --- /dev/null +++ b/.forge-snapshots/NonLinear-ExtendedMultiPointDutchDecay.snap @@ -0,0 +1 @@ +219679 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-LocateCurvePositionMulti.snap b/.forge-snapshots/NonLinear-LocateCurvePositionMulti.snap new file mode 100644 index 00000000..37c44b9c --- /dev/null +++ b/.forge-snapshots/NonLinear-LocateCurvePositionMulti.snap @@ -0,0 +1 @@ +24913 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-LocateCurvePositionSingle.snap b/.forge-snapshots/NonLinear-LocateCurvePositionSingle.snap new file mode 100644 index 00000000..33e69a29 --- /dev/null +++ b/.forge-snapshots/NonLinear-LocateCurvePositionSingle.snap @@ -0,0 +1 @@ +8855 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-MultiPointDutchDecay.snap b/.forge-snapshots/NonLinear-MultiPointDutchDecay.snap new file mode 100644 index 00000000..0e051019 --- /dev/null +++ b/.forge-snapshots/NonLinear-MultiPointDutchDecay.snap @@ -0,0 +1 @@ +52157 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap index beb26b1c..2c352e02 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap @@ -1 +1 @@ -176271 \ 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 abd11ca6..86d87ba4 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap @@ -1 +1 @@ -162263 \ 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 064bdeaa..591acb7c 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap @@ -1 +1 @@ -165548 \ 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 287dcf6c..81370684 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap @@ -1 +1 @@ -146375 \ 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 2f01cb79..17ffcf5f 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap @@ -1 +1 @@ -148955 \ 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 26bb66dd..324315d8 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap @@ -1 +1 @@ -124617 \ No newline at end of file +124643 \ No newline at end of file diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/NonLinearDutchDecayLib.sol index e8f7ee54..e69930c4 100644 --- a/src/lib/NonLinearDutchDecayLib.sol +++ b/src/lib/NonLinearDutchDecayLib.sol @@ -60,35 +60,16 @@ library NonLinearDutchDecayLib { returns (uint16 prev, uint16 next) { Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); - uint16 left = 0; - uint16 right = uint16(curve.relativeAmounts.length) - 1; - uint16 mid; - - while (left <= right) { - mid = left + (right - left) / 2; - uint16 midBlock = relativeBlocks.getElement(mid); - - if (midBlock == currentRelativeBlock) { - return (mid, mid); - } else if (midBlock > currentRelativeBlock) { - if (mid == 0) { - return (mid, mid); - } else if (relativeBlocks.getElement(mid - 1) < currentRelativeBlock) { - return (mid - 1, mid); - } else { - right = mid - 1; - } - } else { - if (mid == curve.relativeAmounts.length - 1) { - return (mid, mid); - } else if (relativeBlocks.getElement(mid + 1) > currentRelativeBlock) { - return (mid, mid + 1); - } else { - left = mid + 1; - } + prev = 0; + next = 0; + while(next < curve.relativeAmounts.length) { + if (relativeBlocks.getElement(next) >= currentRelativeBlock) { + return (prev, next); } + prev = next; + next++; } - revert InvalidDecayCurve(); + return (next - 1, next - 1); } /// @notice returns the linear interpolation between the two points diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index 83befc1e..8e74fe1d 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -2,6 +2,7 @@ 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 {NonLinearDutchDecayLib, InvalidDecayCurve} from "../../src/lib/NonLinearDutchDecayLib.sol"; import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; @@ -16,7 +17,7 @@ contract MockNonLinearDutchDecayLibContract { } } -contract NonLinearDutchDecayLibTest is Test { +contract NonLinearDutchDecayLibTest is Test, GasSnapshot { MockNonLinearDutchDecayLibContract mockNonLinearDutchDecayLibContract = new MockNonLinearDutchDecayLibContract(); function testLocateCurvePositionSingle() public { @@ -24,6 +25,7 @@ contract NonLinearDutchDecayLibTest is Test { relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), relativeAmounts: ArrayBuilder.fillInt(1, 0) }); + snapStart("NonLinear-LocateCurvePositionSingle"); uint16 prev; uint16 next; (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 1); @@ -33,14 +35,15 @@ contract NonLinearDutchDecayLibTest is Test { (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 2); assertEq(prev, 0); assertEq(next, 0); + snapEnd(); } function testLocateCurvePositionMulti() public { uint16[] memory blocks = new uint16[](3); - blocks[0] = 100; // block 200 - blocks[1] = 200; // block 300 - blocks[2] = 300; // block 400 + 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 @@ -48,14 +51,25 @@ contract NonLinearDutchDecayLibTest is Test { NonLinearDecay memory curve = NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); + snapStart("NonLinear-LocateCurvePositionMulti"); uint16 prev; uint16 next; + // currentRelativeBlock shouldn't be less than the first block + // but testing behavior anyways + (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 50); + assertEq(prev, 0); + assertEq(next, 0); + + (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 100); + assertEq(prev, 0); + assertEq(next, 0); + (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 150); assertEq(prev, 0); assertEq(next, 1); (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 200); - assertEq(prev, 1); + assertEq(prev, 0); assertEq(next, 1); (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 250); @@ -63,12 +77,13 @@ contract NonLinearDutchDecayLibTest is Test { assertEq(next, 2); (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 300); - assertEq(prev, 2); + assertEq(prev, 1); assertEq(next, 2); (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 350); assertEq(prev, 2); assertEq(next, 2); + snapEnd(); } function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { @@ -77,6 +92,7 @@ contract NonLinearDutchDecayLibTest is Test { relativeBlocks: toUint256(ArrayBuilder.fillUint16(0, 0)), relativeAmounts: ArrayBuilder.fillInt(0, 0) }); + snapStart("NonLinear-DutchDecayNoDecay"); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); // Single value with 0 amount change @@ -85,6 +101,7 @@ contract NonLinearDutchDecayLibTest is Test { relativeAmounts: ArrayBuilder.fillInt(1, 0) }); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + snapEnd(); } function testDutchDecayNoDecayYet() public { @@ -95,6 +112,7 @@ contract NonLinearDutchDecayLibTest is Test { relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); + snapStart("NonLinear-DutchDecayNoDecayYet"); vm.roll(100); // at decayStartBlock assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); @@ -102,6 +120,7 @@ contract NonLinearDutchDecayLibTest is Test { vm.roll(80); // before decayStartBlock assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + snapEnd(); } function testDutchDecayNoDecayYetNegative() public { @@ -112,6 +131,7 @@ contract NonLinearDutchDecayLibTest is Test { relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); + snapStart("NonLinear-DutchDecayNoDecayYetNegative"); vm.roll(100); // at decayStartBlock assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); @@ -119,6 +139,7 @@ contract NonLinearDutchDecayLibTest is Test { vm.roll(80); // before decayStartBlock assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + snapEnd(); } function testDutchDecay() public { @@ -129,6 +150,7 @@ contract NonLinearDutchDecayLibTest is Test { relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); + snapStart("NonLinear-DutchDecay"); vm.roll(150); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); @@ -140,6 +162,7 @@ contract NonLinearDutchDecayLibTest is Test { vm.roll(190); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + snapEnd(); } function testDutchDecayNegative() public { @@ -150,6 +173,7 @@ contract NonLinearDutchDecayLibTest is Test { relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); + snapStart("NonLinear-DutchDecayNegative"); vm.roll(150); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); @@ -161,6 +185,7 @@ contract NonLinearDutchDecayLibTest is Test { vm.roll(190); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + snapEnd(); } function testDutchDecayFullyDecayed() public { @@ -171,11 +196,13 @@ contract NonLinearDutchDecayLibTest is Test { relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); + snapStart("NonLinear-DutchDecayFullyDecayed"); vm.roll(200); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); vm.warp(250); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); + snapEnd(); } function testDutchDecayFullyDecayedNegative() public { @@ -186,11 +213,13 @@ contract NonLinearDutchDecayLibTest is Test { relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); + snapStart("NonLinear-DutchDecayFullyDecayedNegative"); vm.roll(200); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); vm.warp(250); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + snapEnd(); } function testDutchDecayBounded( @@ -208,9 +237,11 @@ contract NonLinearDutchDecayLibTest is Test { relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, decayDuration)), relativeAmounts: ArrayBuilder.fillInt(1, 0 - int256(decayAmount)) }); + snapStart("NonLinear-DutchDecayBounded"); uint256 decayed = NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); assertGe(decayed, startAmount); assertLe(decayed, startAmount + decayAmount); + snapEnd(); } function testDutchDecayNegative( @@ -230,9 +261,11 @@ contract NonLinearDutchDecayLibTest is Test { relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, decayDuration)), relativeAmounts: ArrayBuilder.fillInt(1, int256(decayAmount)) }); + snapStart("NonLinear-DutchDecayNegative"); uint256 decayed = NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); assertLe(decayed, startAmount); assertGe(decayed, startAmount - decayAmount); + snapEnd(); } function testMultiPointDutchDecay() public { @@ -248,6 +281,7 @@ contract NonLinearDutchDecayLibTest is Test { decayAmounts[2] = 1 ether; // 0 ether NonLinearDecay memory curve = NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); + snapStart("NonLinear-MultiPointDutchDecay"); vm.roll(50); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); @@ -274,25 +308,116 @@ contract NonLinearDutchDecayLibTest is Test { vm.roll(500); assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); + snapEnd(); } - - function testMultiPointDutchDecayGas() public { + + function testExtendedMultiPointDutchDecay() 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 + 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; + NonLinearDecay memory curve = NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); + snapStart("NonLinear-ExtendedMultiPointDutchDecay"); + + vm.roll(50); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + + vm.roll(150); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.05 ether); // halfway between 100 (1 ether) and 200 (1.1 ether) + + vm.roll(200); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); // 1 + 0.1 ether + + vm.roll(250); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.15 ether); // halfway between 200 (1.1 ether) and 300 (1.2 ether) + + vm.roll(300); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.2 ether); // 1 + 0.2 ether vm.roll(350); - NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); - console.log("testMultiPointDutchDecayGas: ", vm.lastCallGas().gasTotalUsed); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.25 ether); // halfway between 300 (1.2 ether) and 400 (1.3 ether) + + vm.roll(400); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.3 ether); // 1 + 0.3 ether + + vm.roll(450); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.35 ether); // halfway between 400 (1.3 ether) and 500 (1.4 ether) + + vm.roll(500); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.4 ether); // 1 + 0.4 ether + + vm.roll(600); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); // 1 + 0.5 ether + + vm.roll(700); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.6 ether); // 1 + 0.6 ether + + vm.roll(800); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.7 ether); // 1 + 0.7 ether + + vm.roll(900); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); // 1 + 0.8 ether + + vm.roll(1000); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); // 1 + 0.9 ether + + vm.roll(1100); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); // 1 + 1 ether + + vm.roll(1200); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); // 1 + 0.9 ether + + vm.roll(1300); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); // 1 + 0.8 ether + + vm.roll(1400); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.7 ether); // 1 + 0.7 ether + + vm.roll(1500); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.6 ether); // 1 + 0.6 ether + + vm.roll(1600); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); // 1 + 0.5 ether + + vm.roll(1650); + assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.45 ether); // 1 + 0.45 ether + + snapEnd(); } /* Invalid order scenarios */ From 98473ae6aa30505a1b9fe9e5629d87ebb508fbb4 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Thu, 29 Aug 2024 14:40:25 -0600 Subject: [PATCH 14/24] Rename to V3 --- ...onLinear-ExtendedMultiPointDutchDecay.snap | 1 - ...ear-DutchDecay.snap => V3-DutchDecay.snap} | 0 ...Bounded.snap => V3-DutchDecayBounded.snap} | 0 ...ed.snap => V3-DutchDecayFullyDecayed.snap} | 0 ...=> V3-DutchDecayFullyDecayedNegative.snap} | 0 ...gative.snap => V3-DutchDecayNegative.snap} | 0 ...NoDecay.snap => V3-DutchDecayNoDecay.snap} | 0 ...yYet.snap => V3-DutchDecayNoDecayYet.snap} | 0 ...p => V3-DutchDecayNoDecayYetNegative.snap} | 0 .../V3-ExtendedMultiPointDutchDecay.snap | 1 + ....snap => V3-LocateCurvePositionMulti.snap} | 0 ...snap => V3-LocateCurvePositionSingle.snap} | 0 ...ecay.snap => V3-MultiPointDutchDecay.snap} | 0 ...rDutchDecayLib.sol => V3DutchDecayLib.sol} | 18 +- ...rDutchOrderLib.sol => V3DutchOrderLib.sol} | 40 ++-- ...derReactor.sol => V3DutchOrderReactor.sol} | 30 +-- test/lib/MathExt.t.sol | 4 +- test/lib/Uint16Array.t.sol | 4 +- ...chDecayLib.t.sol => V3DutchDecayLib.t.sol} | 212 +++++++++--------- 19 files changed, 155 insertions(+), 155 deletions(-) delete mode 100644 .forge-snapshots/NonLinear-ExtendedMultiPointDutchDecay.snap rename .forge-snapshots/{NonLinear-DutchDecay.snap => V3-DutchDecay.snap} (100%) rename .forge-snapshots/{NonLinear-DutchDecayBounded.snap => V3-DutchDecayBounded.snap} (100%) rename .forge-snapshots/{NonLinear-DutchDecayFullyDecayed.snap => V3-DutchDecayFullyDecayed.snap} (100%) rename .forge-snapshots/{NonLinear-DutchDecayFullyDecayedNegative.snap => V3-DutchDecayFullyDecayedNegative.snap} (100%) rename .forge-snapshots/{NonLinear-DutchDecayNegative.snap => V3-DutchDecayNegative.snap} (100%) rename .forge-snapshots/{NonLinear-DutchDecayNoDecay.snap => V3-DutchDecayNoDecay.snap} (100%) rename .forge-snapshots/{NonLinear-DutchDecayNoDecayYet.snap => V3-DutchDecayNoDecayYet.snap} (100%) rename .forge-snapshots/{NonLinear-DutchDecayNoDecayYetNegative.snap => V3-DutchDecayNoDecayYetNegative.snap} (100%) create mode 100644 .forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap rename .forge-snapshots/{NonLinear-LocateCurvePositionMulti.snap => V3-LocateCurvePositionMulti.snap} (100%) rename .forge-snapshots/{NonLinear-LocateCurvePositionSingle.snap => V3-LocateCurvePositionSingle.snap} (100%) rename .forge-snapshots/{NonLinear-MultiPointDutchDecay.snap => V3-MultiPointDutchDecay.snap} (100%) rename src/lib/{NonLinearDutchDecayLib.sol => V3DutchDecayLib.sol} (86%) rename src/lib/{NonLinearDutchOrderLib.sol => V3DutchOrderLib.sol} (83%) rename src/reactors/{NonLinearDutchOrderReactor.sol => V3DutchOrderReactor.sol} (84%) rename test/lib/{NonLinearDutchDecayLib.t.sol => V3DutchDecayLib.t.sol} (53%) diff --git a/.forge-snapshots/NonLinear-ExtendedMultiPointDutchDecay.snap b/.forge-snapshots/NonLinear-ExtendedMultiPointDutchDecay.snap deleted file mode 100644 index 5b397aee..00000000 --- a/.forge-snapshots/NonLinear-ExtendedMultiPointDutchDecay.snap +++ /dev/null @@ -1 +0,0 @@ -219679 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-DutchDecay.snap b/.forge-snapshots/V3-DutchDecay.snap similarity index 100% rename from .forge-snapshots/NonLinear-DutchDecay.snap rename to .forge-snapshots/V3-DutchDecay.snap diff --git a/.forge-snapshots/NonLinear-DutchDecayBounded.snap b/.forge-snapshots/V3-DutchDecayBounded.snap similarity index 100% rename from .forge-snapshots/NonLinear-DutchDecayBounded.snap rename to .forge-snapshots/V3-DutchDecayBounded.snap diff --git a/.forge-snapshots/NonLinear-DutchDecayFullyDecayed.snap b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap similarity index 100% rename from .forge-snapshots/NonLinear-DutchDecayFullyDecayed.snap rename to .forge-snapshots/V3-DutchDecayFullyDecayed.snap diff --git a/.forge-snapshots/NonLinear-DutchDecayFullyDecayedNegative.snap b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap similarity index 100% rename from .forge-snapshots/NonLinear-DutchDecayFullyDecayedNegative.snap rename to .forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap diff --git a/.forge-snapshots/NonLinear-DutchDecayNegative.snap b/.forge-snapshots/V3-DutchDecayNegative.snap similarity index 100% rename from .forge-snapshots/NonLinear-DutchDecayNegative.snap rename to .forge-snapshots/V3-DutchDecayNegative.snap diff --git a/.forge-snapshots/NonLinear-DutchDecayNoDecay.snap b/.forge-snapshots/V3-DutchDecayNoDecay.snap similarity index 100% rename from .forge-snapshots/NonLinear-DutchDecayNoDecay.snap rename to .forge-snapshots/V3-DutchDecayNoDecay.snap diff --git a/.forge-snapshots/NonLinear-DutchDecayNoDecayYet.snap b/.forge-snapshots/V3-DutchDecayNoDecayYet.snap similarity index 100% rename from .forge-snapshots/NonLinear-DutchDecayNoDecayYet.snap rename to .forge-snapshots/V3-DutchDecayNoDecayYet.snap diff --git a/.forge-snapshots/NonLinear-DutchDecayNoDecayYetNegative.snap b/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap similarity index 100% rename from .forge-snapshots/NonLinear-DutchDecayNoDecayYetNegative.snap rename to .forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap diff --git a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap new file mode 100644 index 00000000..847b312b --- /dev/null +++ b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap @@ -0,0 +1 @@ +219678 \ No newline at end of file diff --git a/.forge-snapshots/NonLinear-LocateCurvePositionMulti.snap b/.forge-snapshots/V3-LocateCurvePositionMulti.snap similarity index 100% rename from .forge-snapshots/NonLinear-LocateCurvePositionMulti.snap rename to .forge-snapshots/V3-LocateCurvePositionMulti.snap diff --git a/.forge-snapshots/NonLinear-LocateCurvePositionSingle.snap b/.forge-snapshots/V3-LocateCurvePositionSingle.snap similarity index 100% rename from .forge-snapshots/NonLinear-LocateCurvePositionSingle.snap rename to .forge-snapshots/V3-LocateCurvePositionSingle.snap diff --git a/.forge-snapshots/NonLinear-MultiPointDutchDecay.snap b/.forge-snapshots/V3-MultiPointDutchDecay.snap similarity index 100% rename from .forge-snapshots/NonLinear-MultiPointDutchDecay.snap rename to .forge-snapshots/V3-MultiPointDutchDecay.snap diff --git a/src/lib/NonLinearDutchDecayLib.sol b/src/lib/V3DutchDecayLib.sol similarity index 86% rename from src/lib/NonLinearDutchDecayLib.sol rename to src/lib/V3DutchDecayLib.sol index e69930c4..201a8967 100644 --- a/src/lib/NonLinearDutchDecayLib.sol +++ b/src/lib/V3DutchDecayLib.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {OutputToken, InputToken} from "../base/ReactorStructs.sol"; -import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../lib/NonLinearDutchOrderLib.sol"; +import {V3DutchOutput, V3DutchInput, V3Decay} from "../lib/V3DutchOrderLib.sol"; import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; import {sub} from "./MathExt.sol"; import {Uint16Array, fromUnderlying} from "../types/Uint16Array.sol"; @@ -11,7 +11,7 @@ import {Uint16Array, fromUnderlying} from "../types/Uint16Array.sol"; error InvalidDecayCurve(); /// @notice helpers for handling non-linear dutch order objects -library NonLinearDutchDecayLib { +library V3DutchDecayLib { using FixedPointMathLib for uint256; using {sub} for uint256; @@ -20,7 +20,7 @@ library NonLinearDutchDecayLib { /// @param curve The curve to search /// @param startAmount The absolute start amount /// @param decayStartBlock The absolute start block of the decay - function decay(NonLinearDecay memory curve, uint256 startAmount, uint256 decayStartBlock) + function decay(V3Decay memory curve, uint256 startAmount, uint256 decayStartBlock) internal view returns (uint256 decayedAmount) @@ -54,7 +54,7 @@ library NonLinearDutchDecayLib { /// @param currentRelativeBlock The current relative position /// @return prev The relative block before the current position /// @return next The relative block after the current position - function locateCurvePosition(NonLinearDecay memory curve, uint16 currentRelativeBlock) + function locateCurvePosition(V3Decay memory curve, uint16 currentRelativeBlock) internal pure returns (uint16 prev, uint16 next) @@ -103,12 +103,12 @@ library NonLinearDutchDecayLib { /// @param output The output to decay /// @param decayStartBlock The block to start decaying /// @return result a decayed output - function decay(NonLinearDutchOutput memory output, uint256 decayStartBlock) + function decay(V3DutchOutput memory output, uint256 decayStartBlock) internal view returns (OutputToken memory result) { - uint256 decayedOutput = NonLinearDutchDecayLib.decay(output.curve, output.startAmount, decayStartBlock); + uint256 decayedOutput = V3DutchDecayLib.decay(output.curve, output.startAmount, decayStartBlock); result = OutputToken(output.token, decayedOutput, output.recipient); } @@ -116,7 +116,7 @@ library NonLinearDutchDecayLib { /// @param outputs The output array to decay /// @param decayStartBlock The block to start decaying /// @return result a decayed output array - function decay(NonLinearDutchOutput[] memory outputs, uint256 decayStartBlock) + function decay(V3DutchOutput[] memory outputs, uint256 decayStartBlock) internal view returns (OutputToken[] memory result) @@ -134,12 +134,12 @@ library NonLinearDutchDecayLib { /// @param input The input to decay /// @param decayStartBlock The block to start decaying /// @return result a decayed input - function decay(NonLinearDutchInput memory input, uint256 decayStartBlock) + function decay(V3DutchInput memory input, uint256 decayStartBlock) internal view returns (InputToken memory result) { - uint256 decayedInput = NonLinearDutchDecayLib.decay(input.curve, input.startAmount, decayStartBlock); + uint256 decayedInput = V3DutchDecayLib.decay(input.curve, input.startAmount, decayStartBlock); result = InputToken(input.token, decayedInput, input.maxAmount); } } diff --git a/src/lib/NonLinearDutchOrderLib.sol b/src/lib/V3DutchOrderLib.sol similarity index 83% rename from src/lib/NonLinearDutchOrderLib.sol rename to src/lib/V3DutchOrderLib.sol index 59abbc30..f539d2ee 100644 --- a/src/lib/NonLinearDutchOrderLib.sol +++ b/src/lib/V3DutchOrderLib.sol @@ -19,15 +19,15 @@ struct CosignerData { uint256[] outputAmounts; } -struct NonLinearDutchOrder { +struct V3DutchOrder { // generic order information OrderInfo info; // The address which must cosign the full order address cosigner; // The tokens that the swapper will provide when settling the order - NonLinearDutchInput baseInput; + V3DutchInput baseInput; // The tokens that must be received to satisfy the order - NonLinearDutchOutput[] baseOutputs; + V3DutchOutput[] baseOutputs; // signed over by the cosigner CosignerData cosignerData; // signature from the cosigner over (orderHash || cosignerData) @@ -36,7 +36,7 @@ struct NonLinearDutchOrder { /// @dev The changes in tokens (positive or negative) to subtract from the start amount /// @dev The relativeBlocks should be strictly increasing -struct NonLinearDecay { +struct V3Decay { // 16 uint16 values packed // Can represent curves with points 2^16 blocks into the future uint256 relativeBlocks; @@ -44,46 +44,46 @@ struct NonLinearDecay { } /// @dev An amount of input tokens that increases non-linearly over time -struct NonLinearDutchInput { +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 - NonLinearDecay curve; + V3Decay curve; // The max amount of the curve uint256 maxAmount; } /// @dev An amount of output tokens that decreases non-linearly over time -struct NonLinearDutchOutput { +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 - NonLinearDecay curve; + V3Decay curve; // The address who must receive the tokens to satisfy the order address recipient; } /// @notice helpers for handling custom curve order objects -library NonLinearDutchOrderLib { +library V3DutchOrderLib { using OrderInfoLib for OrderInfo; bytes internal constant NON_LINEAR_DUTCH_ORDER_TYPE = abi.encodePacked( - "NonLinearDutchOrder(", + "V3DutchOrder(", "OrderInfo info,", "address cosigner,", - "NonLinearDutchInput baseInput,", - "NonLinearDutchOutput[] baseOutputs)" + "V3DutchInput baseInput,", + "V3DutchOutput[] baseOutputs)" ); bytes internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE = abi.encodePacked( - "NonLinearDutchOutput(", "address token,", "uint256 startAmount,", "NonLinearDecay curve,", "address recipient)" + "V3DutchOutput(", "address token,", "uint256 startAmount,", "V3Decay curve,", "address recipient)" ); bytes32 internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH = keccak256(NON_LINEAR_DUTCH_OUTPUT_TYPE); bytes internal constant NON_LINEAR_DECAY_TYPE = - abi.encodePacked("NonLinearDecay(", "uint256 relativeBlocks,", "int256[] relativeAmounts)"); + abi.encodePacked("V3Decay(", "uint256 relativeBlocks,", "int256[] relativeAmounts)"); bytes32 internal constant NON_LINEAR_DECAY_TYPE_HASH = keccak256(NON_LINEAR_DECAY_TYPE); bytes internal constant ORDER_TYPE = abi.encodePacked( @@ -94,7 +94,7 @@ library NonLinearDutchOrderLib { /// @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( - "NonLinearDutchOrder witness)", + "V3DutchOrder witness)", NON_LINEAR_DECAY_TYPE, NON_LINEAR_DUTCH_ORDER_TYPE, NON_LINEAR_DUTCH_OUTPUT_TYPE, @@ -103,7 +103,7 @@ library NonLinearDutchOrderLib { ) ); - function hash(NonLinearDecay memory curve) internal pure returns (bytes32) { + function hash(V3Decay memory curve) internal pure returns (bytes32) { return keccak256( abi.encode( NON_LINEAR_DECAY_TYPE_HASH, curve.relativeBlocks, keccak256(abi.encodePacked(curve.relativeAmounts)) @@ -114,7 +114,7 @@ library NonLinearDutchOrderLib { /// @notice hash the given input /// @param input the input to hash /// @return the eip-712 input hash - function hash(NonLinearDutchInput memory input) internal pure returns (bytes32) { + function hash(V3DutchInput memory input) internal pure returns (bytes32) { return keccak256( abi.encode( NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH, input.token, input.startAmount, hash(input.curve), input.maxAmount @@ -125,7 +125,7 @@ library NonLinearDutchOrderLib { /// @notice hash the given output /// @param output the output to hash /// @return the eip-712 output hash - function hash(NonLinearDutchOutput memory output) internal pure returns (bytes32) { + function hash(V3DutchOutput memory output) internal pure returns (bytes32) { return keccak256( abi.encode( NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH, @@ -140,7 +140,7 @@ library NonLinearDutchOrderLib { /// @notice hash the given outputs /// @param outputs the outputs to hash /// @return the eip-712 outputs hash - function hash(NonLinearDutchOutput[] memory outputs) internal pure returns (bytes32) { + function hash(V3DutchOutput[] memory outputs) internal pure returns (bytes32) { unchecked { bytes memory packedHashes = new bytes(32 * outputs.length); @@ -158,7 +158,7 @@ library NonLinearDutchOrderLib { /// @notice hash the given order /// @param order the order to hash /// @return the eip-712 order hash - function hash(NonLinearDutchOrder memory order) internal pure returns (bytes32) { + function hash(V3DutchOrder memory order) internal pure returns (bytes32) { return keccak256( abi.encode( ORDER_TYPE_HASH, order.info.hash(), order.cosigner, hash(order.baseInput), hash(order.baseOutputs) diff --git a/src/reactors/NonLinearDutchOrderReactor.sol b/src/reactors/V3DutchOrderReactor.sol similarity index 84% rename from src/reactors/NonLinearDutchOrderReactor.sol rename to src/reactors/V3DutchOrderReactor.sol index 0b02da29..099117c4 100644 --- a/src/reactors/NonLinearDutchOrderReactor.sol +++ b/src/reactors/V3DutchOrderReactor.sol @@ -5,14 +5,14 @@ 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 {V3DutchDecayLib} from "../lib/V3DutchDecayLib.sol"; import { - NonLinearDutchOrderLib, - NonLinearDutchOrder, + V3DutchOrderLib, + V3DutchOrder, CosignerData, - NonLinearDutchOutput, - NonLinearDutchInput -} from "../lib/NonLinearDutchOrderLib.sol"; + V3DutchOutput, + V3DutchInput +} from "../lib/V3DutchOrderLib.sol"; import {SignedOrder, ResolvedOrder} from "../base/ReactorStructs.sol"; /// @notice Reactor for non-linear dutch orders @@ -24,11 +24,11 @@ import {SignedOrder, ResolvedOrder} from "../base/ReactorStructs.sol"; /// - 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 NonLinearDutchOrderReactor is BaseReactor { +contract V3DutchOrderReactor is BaseReactor { using Permit2Lib for ResolvedOrder; - using NonLinearDutchOrderLib for NonLinearDutchOrder; - using NonLinearDutchDecayLib for NonLinearDutchOutput[]; - using NonLinearDutchDecayLib for NonLinearDutchInput; + using V3DutchOrderLib for V3DutchOrder; + using V3DutchDecayLib for V3DutchOutput[]; + using V3DutchDecayLib for V3DutchInput; using ExclusivityLib for ResolvedOrder; /// @notice thrown when an order's deadline is passed @@ -53,7 +53,7 @@ contract NonLinearDutchOrderReactor is BaseReactor { override returns (ResolvedOrder memory resolvedOrder) { - NonLinearDutchOrder memory order = abi.decode(signedOrder.order, (NonLinearDutchOrder)); + 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(); @@ -84,12 +84,12 @@ contract NonLinearDutchOrderReactor is BaseReactor { order.transferDetails(to), order.info.swapper, order.hash, - NonLinearDutchOrderLib.PERMIT2_ORDER_TYPE, + V3DutchOrderLib.PERMIT2_ORDER_TYPE, order.sig ); } - function _updateWithCosignerAmounts(NonLinearDutchOrder memory order) internal pure { + function _updateWithCosignerAmounts(V3DutchOrder memory order) internal pure { if (order.cosignerData.inputAmount != 0) { if (order.cosignerData.inputAmount > order.baseInput.startAmount) { revert InvalidCosignerInput(); @@ -101,7 +101,7 @@ contract NonLinearDutchOrderReactor is BaseReactor { revert InvalidCosignerOutput(); } for (uint256 i = 0; i < order.baseOutputs.length; i++) { - NonLinearDutchOutput memory output = order.baseOutputs[i]; + V3DutchOutput memory output = order.baseOutputs[i]; uint256 outputAmount = order.cosignerData.outputAmounts[i]; if (outputAmount != 0) { if (outputAmount < output.startAmount) { @@ -115,7 +115,7 @@ contract NonLinearDutchOrderReactor is BaseReactor { /// @notice validate the dutch order fields /// - deadline must have not passed /// @dev Throws if the order is invalid - function _validateOrder(bytes32 orderHash, NonLinearDutchOrder memory order) internal pure { + function _validateOrder(bytes32 orderHash, V3DutchOrder memory order) internal pure { (bytes32 r, bytes32 s) = abi.decode(order.cosignature, (bytes32, bytes32)); uint8 v = uint8(order.cosignature[64]); // cosigner signs over (orderHash || cosignerData) diff --git a/test/lib/MathExt.t.sol b/test/lib/MathExt.t.sol index 702222ed..c5134920 100644 --- a/test/lib/MathExt.t.sol +++ b/test/lib/MathExt.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import {Test} from "forge-std/Test.sol"; -import {NonLinearDutchDecayLib} from "../../src/lib/NonLinearDutchDecayLib.sol"; -import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; +import {V3DutchDecayLib} from "../../src/lib/V3DutchDecayLib.sol"; +import {V3DutchOutput, V3DutchInput, V3Decay} from "../../src/lib/V3DutchOrderLib.sol"; import {sub, NegativeUint} from "../../src/lib/MathExt.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; diff --git a/test/lib/Uint16Array.t.sol b/test/lib/Uint16Array.t.sol index 2fb9d1c4..44175742 100644 --- a/test/lib/Uint16Array.t.sol +++ b/test/lib/Uint16Array.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import {Test} from "forge-std/Test.sol"; -import {NonLinearDutchDecayLib} from "../../src/lib/NonLinearDutchDecayLib.sol"; -import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; +import {V3DutchDecayLib} from "../../src/lib/V3DutchDecayLib.sol"; +import {V3DutchOutput, V3DutchInput, V3Decay} from "../../src/lib/V3DutchOrderLib.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; import {Uint16Array, toUint16Array, InvalidArrLength, IndexOutOfBounds} from "../../src/types/Uint16Array.sol"; diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/V3DutchDecayLib.t.sol similarity index 53% rename from test/lib/NonLinearDutchDecayLib.t.sol rename to test/lib/V3DutchDecayLib.t.sol index 8e74fe1d..ee7fee7d 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/V3DutchDecayLib.t.sol @@ -4,35 +4,35 @@ 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 {NonLinearDutchDecayLib, InvalidDecayCurve} from "../../src/lib/NonLinearDutchDecayLib.sol"; -import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol"; +import {V3DutchDecayLib, InvalidDecayCurve} from "../../src/lib/V3DutchDecayLib.sol"; +import {V3DutchOutput, V3DutchInput, V3Decay} from "../../src/lib/V3DutchOrderLib.sol"; import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; import {NegativeUint} from "../../src/lib/MathExt.sol"; -/// @notice mock contract to test NonLinearDutchDecayLib functionality -contract MockNonLinearDutchDecayLibContract { - function decay(NonLinearDecay memory curve, uint256 startAmount, uint256 decayStartBlock) public view { - NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); +/// @notice mock contract to test V3DutchDecayLib functionality +contract MockV3DutchDecayLibContract { + function decay(V3Decay memory curve, uint256 startAmount, uint256 decayStartBlock) public view { + V3DutchDecayLib.decay(curve, startAmount, decayStartBlock); } } -contract NonLinearDutchDecayLibTest is Test, GasSnapshot { - MockNonLinearDutchDecayLibContract mockNonLinearDutchDecayLibContract = new MockNonLinearDutchDecayLibContract(); +contract V3DutchDecayLibTest is Test, GasSnapshot { + MockV3DutchDecayLibContract mockV3DutchDecayLibContract = new MockV3DutchDecayLibContract(); function testLocateCurvePositionSingle() public { - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), relativeAmounts: ArrayBuilder.fillInt(1, 0) }); - snapStart("NonLinear-LocateCurvePositionSingle"); + snapStart("V3-LocateCurvePositionSingle"); uint16 prev; uint16 next; - (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 1); + (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 1); assertEq(prev, 0); assertEq(next, 0); - (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 2); + (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 2); assertEq(prev, 0); assertEq(next, 0); snapEnd(); @@ -48,39 +48,39 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { decayAmounts[0] = -1 ether; // 2 ether decayAmounts[1] = 0 ether; // 1 ether decayAmounts[2] = 1 ether; // 0 ether - NonLinearDecay memory curve = - NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); + V3Decay memory curve = + V3Decay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); - snapStart("NonLinear-LocateCurvePositionMulti"); + snapStart("V3-LocateCurvePositionMulti"); uint16 prev; uint16 next; // currentRelativeBlock shouldn't be less than the first block // but testing behavior anyways - (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 50); + (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 50); assertEq(prev, 0); assertEq(next, 0); - (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 100); + (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 100); assertEq(prev, 0); assertEq(next, 0); - (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 150); + (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 150); assertEq(prev, 0); assertEq(next, 1); - (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 200); + (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 200); assertEq(prev, 0); assertEq(next, 1); - (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 250); + (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 250); assertEq(prev, 1); assertEq(next, 2); - (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 300); + (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 300); assertEq(prev, 1); assertEq(next, 2); - (prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 350); + (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 350); assertEq(prev, 2); assertEq(next, 2); snapEnd(); @@ -88,19 +88,19 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { // Empty curve - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(0, 0)), relativeAmounts: ArrayBuilder.fillInt(0, 0) }); - snapStart("NonLinear-DutchDecayNoDecay"); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + snapStart("V3-DutchDecayNoDecay"); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); // Single value with 0 amount change - curve = NonLinearDecay({ + curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), relativeAmounts: ArrayBuilder.fillInt(1, 0) }); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); snapEnd(); } @@ -108,18 +108,18 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { uint256 decayStartBlock = 200; uint256 startAmount = 1 ether; int256 decayAmount = -1 ether; - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); - snapStart("NonLinear-DutchDecayNoDecayYet"); + snapStart("V3-DutchDecayNoDecayYet"); vm.roll(100); // at decayStartBlock - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); vm.roll(80); // before decayStartBlock - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); snapEnd(); } @@ -127,18 +127,18 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { uint256 decayStartBlock = 200; uint256 startAmount = 1 ether; int256 decayAmount = 1 ether; - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); - snapStart("NonLinear-DutchDecayNoDecayYetNegative"); + snapStart("V3-DutchDecayNoDecayYetNegative"); vm.roll(100); // at decayStartBlock - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); vm.roll(80); // before decayStartBlock - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); snapEnd(); } @@ -146,22 +146,22 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { uint256 decayStartBlock = 100; uint256 startAmount = 1 ether; int256 decayAmount = -1 ether; - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); - snapStart("NonLinear-DutchDecay"); + snapStart("V3-DutchDecay"); vm.roll(150); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); vm.roll(180); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); vm.roll(110); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); vm.roll(190); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); snapEnd(); } @@ -169,22 +169,22 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { uint256 decayStartBlock = 100; uint256 startAmount = 2 ether; int256 decayAmount = 1 ether; - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); - snapStart("NonLinear-DutchDecayNegative"); + snapStart("V3-DutchDecayNegative"); vm.roll(150); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); vm.roll(180); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.2 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.2 ether); vm.roll(110); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); vm.roll(190); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); snapEnd(); } @@ -192,16 +192,16 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { uint256 decayStartBlock = 100; uint256 startAmount = 1 ether; int256 decayAmount = -1 ether; - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); - snapStart("NonLinear-DutchDecayFullyDecayed"); + snapStart("V3-DutchDecayFullyDecayed"); vm.roll(200); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); vm.warp(250); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); snapEnd(); } @@ -209,16 +209,16 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { uint256 decayStartBlock = 100; uint256 startAmount = 2 ether; int256 decayAmount = 1 ether; - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); - snapStart("NonLinear-DutchDecayFullyDecayedNegative"); + snapStart("V3-DutchDecayFullyDecayedNegative"); vm.roll(200); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); vm.warp(250); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); snapEnd(); } @@ -233,12 +233,12 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { vm.assume(startAmount <= UINT256_MAX - decayAmount); vm.assume(decayDuration > 0); - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, decayDuration)), relativeAmounts: ArrayBuilder.fillInt(1, 0 - int256(decayAmount)) }); - snapStart("NonLinear-DutchDecayBounded"); - uint256 decayed = NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + snapStart("V3-DutchDecayBounded"); + uint256 decayed = V3DutchDecayLib.decay(curve, startAmount, decayStartBlock); assertGe(decayed, startAmount); assertLe(decayed, startAmount + decayAmount); snapEnd(); @@ -257,12 +257,12 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { vm.assume(startAmount <= UINT256_MAX - decayAmount); vm.assume(decayDuration > 0); - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, decayDuration)), relativeAmounts: ArrayBuilder.fillInt(1, int256(decayAmount)) }); - snapStart("NonLinear-DutchDecayNegative"); - uint256 decayed = NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + snapStart("V3-DutchDecayNegative"); + uint256 decayed = V3DutchDecayLib.decay(curve, startAmount, decayStartBlock); assertLe(decayed, startAmount); assertGe(decayed, startAmount - decayAmount); snapEnd(); @@ -279,35 +279,35 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { decayAmounts[0] = -1 ether; // 2 ether decayAmounts[1] = 0 ether; // 1 ether decayAmounts[2] = 1 ether; // 0 ether - NonLinearDecay memory curve = - NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); - snapStart("NonLinear-MultiPointDutchDecay"); + V3Decay memory curve = + V3Decay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); + snapStart("V3-MultiPointDutchDecay"); vm.roll(50); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); vm.roll(150); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); vm.roll(200); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); vm.roll(210); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); vm.roll(290); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); vm.roll(300); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); vm.roll(350); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.5 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.5 ether); vm.roll(400); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); vm.roll(500); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); snapEnd(); } @@ -350,72 +350,72 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { decayAmounts[14] = -0.5 ether; decayAmounts[15] = -0.4 ether; - NonLinearDecay memory curve = - NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); - snapStart("NonLinear-ExtendedMultiPointDutchDecay"); + V3Decay memory curve = + V3Decay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); + snapStart("V3-ExtendedMultiPointDutchDecay"); vm.roll(50); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); vm.roll(150); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.05 ether); // halfway between 100 (1 ether) and 200 (1.1 ether) + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.05 ether); // halfway between 100 (1 ether) and 200 (1.1 ether) vm.roll(200); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); // 1 + 0.1 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); // 1 + 0.1 ether vm.roll(250); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.15 ether); // halfway between 200 (1.1 ether) and 300 (1.2 ether) + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.15 ether); // halfway between 200 (1.1 ether) and 300 (1.2 ether) vm.roll(300); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.2 ether); // 1 + 0.2 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.2 ether); // 1 + 0.2 ether vm.roll(350); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.25 ether); // halfway between 300 (1.2 ether) and 400 (1.3 ether) + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.25 ether); // halfway between 300 (1.2 ether) and 400 (1.3 ether) vm.roll(400); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.3 ether); // 1 + 0.3 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.3 ether); // 1 + 0.3 ether vm.roll(450); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.35 ether); // halfway between 400 (1.3 ether) and 500 (1.4 ether) + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.35 ether); // halfway between 400 (1.3 ether) and 500 (1.4 ether) vm.roll(500); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.4 ether); // 1 + 0.4 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.4 ether); // 1 + 0.4 ether vm.roll(600); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); // 1 + 0.5 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); // 1 + 0.5 ether vm.roll(700); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.6 ether); // 1 + 0.6 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.6 ether); // 1 + 0.6 ether vm.roll(800); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.7 ether); // 1 + 0.7 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.7 ether); // 1 + 0.7 ether vm.roll(900); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); // 1 + 0.8 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); // 1 + 0.8 ether vm.roll(1000); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); // 1 + 0.9 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); // 1 + 0.9 ether vm.roll(1100); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); // 1 + 1 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); // 1 + 1 ether vm.roll(1200); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); // 1 + 0.9 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); // 1 + 0.9 ether vm.roll(1300); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); // 1 + 0.8 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); // 1 + 0.8 ether vm.roll(1400); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.7 ether); // 1 + 0.7 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.7 ether); // 1 + 0.7 ether vm.roll(1500); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.6 ether); // 1 + 0.6 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.6 ether); // 1 + 0.6 ether vm.roll(1600); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); // 1 + 0.5 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); // 1 + 0.5 ether vm.roll(1650); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.45 ether); // 1 + 0.45 ether + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.45 ether); // 1 + 0.45 ether snapEnd(); } @@ -433,47 +433,47 @@ contract NonLinearDutchDecayLibTest is Test, GasSnapshot { decayAmounts[0] = -1 ether; // 2 ether decayAmounts[1] = 0 ether; // 1 ether decayAmounts[2] = 1 ether; // 0 ether - NonLinearDecay memory curve = - NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); + V3Decay memory curve = + V3Decay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); vm.roll(350); - assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.25 ether); + assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.25 ether); } function testDutchDecayToNegative() public { uint256 decayStartBlock = 100; uint256 startAmount = 1 ether; int256 decayAmount = 2 ether; - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(150); vm.expectRevert(NegativeUint.selector); - mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + mockV3DutchDecayLibContract.decay(curve, startAmount, decayStartBlock); } function testDutchOverflowDecay() public { uint256 decayStartBlock = 100; uint256 startAmount = 1 ether; int256 decayAmount = -(2 ** 255 - 1); - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) }); vm.roll(150); vm.expectRevert(); - mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + mockV3DutchDecayLibContract.decay(curve, startAmount, decayStartBlock); } function testDutchMismatchedDecay() public { uint256 decayStartBlock = 100; uint256 startAmount = 1 ether; - NonLinearDecay memory curve = NonLinearDecay({ + V3Decay memory curve = V3Decay({ relativeBlocks: toUint256(ArrayBuilder.fillUint16(16, 1)), relativeAmounts: ArrayBuilder.fillInt(17, 0) }); vm.roll(150); vm.expectRevert(InvalidDecayCurve.selector); - mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + mockV3DutchDecayLibContract.decay(curve, startAmount, decayStartBlock); } } From 29f6ed956cf7250b23cdc30a542835d18f5624b9 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Sat, 31 Aug 2024 16:00:20 -0600 Subject: [PATCH 15/24] Reactor tests pt1, refactor tests, fmt --- ...V3DutchOrder-BaseExecuteSingleWithFee.snap | 1 + .../Base-V3DutchOrder-ExecuteBatch.snap | 1 + ...utchOrder-ExecuteBatchMultipleOutputs.snap | 1 + ...teBatchMultipleOutputsDifferentTokens.snap | 1 + ...V3DutchOrder-ExecuteBatchNativeOutput.snap | 1 + .../Base-V3DutchOrder-ExecuteSingle.snap | 1 + ...3DutchOrder-ExecuteSingleNativeOutput.snap | 1 + ...-V3DutchOrder-ExecuteSingleValidation.snap | 1 + .../Base-V3DutchOrder-InputOverride.snap | 1 + .../Base-V3DutchOrder-RevertInvalidNonce.snap | 1 + .../Base-V3DutchOrder-V3-ExclusiveFiller.snap | 1 + .../Base-V3DutchOrder-V3-InputOverride.snap | 1 + .../Base-V3DutchOrder-V3-OutputOverride.snap | 1 + .forge-snapshots/V3-DutchDecayBounded.snap | 2 +- .../V3-DutchDecayFullyDecayed.snap | 2 +- .../V3-DutchDecayFullyDecayedNegative.snap | 2 +- .forge-snapshots/V3-DutchDecayNoDecay.snap | 2 +- .../V3-ExtendedMultiPointDutchDecay.snap | 2 +- .../V3-LocateCurvePositionMulti.snap | 2 +- .../V3-LocateCurvePositionSingle.snap | 2 +- .forge-snapshots/V3-MultiPointDutchDecay.snap | 2 +- src/lib/ExclusivityLib.sol | 6 +- ...ecayLib.sol => NonlinearDutchDecayLib.sol} | 31 +- src/lib/V3DutchOrderLib.sol | 12 +- src/reactors/V3DutchOrderReactor.sol | 14 +- src/types/Uint16Array.sol | 4 +- test/lib/ExclusivityLib.t.sol | 30 +- test/lib/MathExt.t.sol | 4 +- test/lib/NonLinearDutchDecayLib.t.sol | 431 ++++++++++++++++ test/lib/Uint16Array.t.sol | 5 +- test/lib/V3DutchDecayLib.t.sol | 479 ------------------ test/reactors/V3DutchOrderReactor.t.sol | 460 +++++++++++++++++ test/util/CurveBuilder.sol | 34 ++ test/util/OutputsBuilder.sol | 35 ++ test/util/PermitSignature.sol | 21 + test/util/mock/MockExclusivityLib.sol | 1 + 36 files changed, 1059 insertions(+), 537 deletions(-) create mode 100644 .forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-InputOverride.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap create mode 100644 .forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap rename src/lib/{V3DutchDecayLib.sol => NonlinearDutchDecayLib.sol} (82%) create mode 100644 test/lib/NonLinearDutchDecayLib.t.sol delete mode 100644 test/lib/V3DutchDecayLib.t.sol create mode 100644 test/reactors/V3DutchOrderReactor.t.sol create mode 100644 test/util/CurveBuilder.sol diff --git a/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap new file mode 100644 index 00000000..2825e932 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap @@ -0,0 +1 @@ +192981 \ 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..2ab57fb9 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap @@ -0,0 +1 @@ +219434 \ 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..3c001253 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap @@ -0,0 +1 @@ +231444 \ 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..eef5a09a --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -0,0 +1 @@ +287371 \ 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..b4dbf0f4 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap @@ -0,0 +1 @@ +212953 \ 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..d1ade6b4 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap @@ -0,0 +1 @@ +159369 \ 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..eab5e06d --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap @@ -0,0 +1 @@ +144927 \ 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..d23dabd5 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap @@ -0,0 +1 @@ +168675 \ 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..0e0324bc --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap @@ -0,0 +1 @@ +37963 \ 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..32fb1f75 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap @@ -0,0 +1 @@ +163306 \ 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..fde53516 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap @@ -0,0 +1 @@ +163383 \ 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..c7d15444 --- /dev/null +++ b/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap @@ -0,0 +1 @@ +163332 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayBounded.snap b/.forge-snapshots/V3-DutchDecayBounded.snap index 3af7d09a..db604d62 100644 --- a/.forge-snapshots/V3-DutchDecayBounded.snap +++ b/.forge-snapshots/V3-DutchDecayBounded.snap @@ -1 +1 @@ -1070 \ No newline at end of file +5778 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap index a8966006..a901a15c 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap @@ -1 +1 @@ -14834 \ No newline at end of file +14782 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap index 3dabfaf8..9ce7dd3d 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap @@ -1 +1 @@ -14686 \ No newline at end of file +14634 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNoDecay.snap b/.forge-snapshots/V3-DutchDecayNoDecay.snap index 2c6feee2..d5fffd06 100644 --- a/.forge-snapshots/V3-DutchDecayNoDecay.snap +++ b/.forge-snapshots/V3-DutchDecayNoDecay.snap @@ -1 +1 @@ -4721 \ No newline at end of file +5457 \ No newline at end of file diff --git a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap index 847b312b..e4368d80 100644 --- a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap @@ -1 +1 @@ -219678 \ No newline at end of file +219186 \ No newline at end of file diff --git a/.forge-snapshots/V3-LocateCurvePositionMulti.snap b/.forge-snapshots/V3-LocateCurvePositionMulti.snap index 37c44b9c..6c884d81 100644 --- a/.forge-snapshots/V3-LocateCurvePositionMulti.snap +++ b/.forge-snapshots/V3-LocateCurvePositionMulti.snap @@ -1 +1 @@ -24913 \ No newline at end of file +24798 \ No newline at end of file diff --git a/.forge-snapshots/V3-LocateCurvePositionSingle.snap b/.forge-snapshots/V3-LocateCurvePositionSingle.snap index 33e69a29..111a659b 100644 --- a/.forge-snapshots/V3-LocateCurvePositionSingle.snap +++ b/.forge-snapshots/V3-LocateCurvePositionSingle.snap @@ -1 +1 @@ -8855 \ No newline at end of file +8820 \ No newline at end of file diff --git a/.forge-snapshots/V3-MultiPointDutchDecay.snap b/.forge-snapshots/V3-MultiPointDutchDecay.snap index 0e051019..a605cd33 100644 --- a/.forge-snapshots/V3-MultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-MultiPointDutchDecay.snap @@ -1 +1 @@ -52157 \ No newline at end of file +51976 \ No newline at end of file diff --git a/src/lib/ExclusivityLib.sol b/src/lib/ExclusivityLib.sol index bd1e4a62..00613777 100644 --- a/src/lib/ExclusivityLib.sol +++ b/src/lib/ExclusivityLib.sol @@ -84,7 +84,11 @@ library ExclusivityLib { /// @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 exclusivityEnd, uint256 currentPosition) internal view returns (bool) { + 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/V3DutchDecayLib.sol b/src/lib/NonlinearDutchDecayLib.sol similarity index 82% rename from src/lib/V3DutchDecayLib.sol rename to src/lib/NonlinearDutchDecayLib.sol index 201a8967..b8927759 100644 --- a/src/lib/V3DutchDecayLib.sol +++ b/src/lib/NonlinearDutchDecayLib.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {OutputToken, InputToken} from "../base/ReactorStructs.sol"; -import {V3DutchOutput, V3DutchInput, V3Decay} from "../lib/V3DutchOrderLib.sol"; +import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../lib/V3DutchOrderLib.sol"; import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; import {sub} from "./MathExt.sol"; import {Uint16Array, fromUnderlying} from "../types/Uint16Array.sol"; @@ -11,8 +11,7 @@ import {Uint16Array, fromUnderlying} from "../types/Uint16Array.sol"; error InvalidDecayCurve(); /// @notice helpers for handling non-linear dutch order objects -library V3DutchDecayLib { - +library NonlinearDutchDecayLib { using FixedPointMathLib for uint256; using {sub} for uint256; @@ -20,13 +19,13 @@ library V3DutchDecayLib { /// @param curve The curve to search /// @param startAmount The absolute start amount /// @param decayStartBlock The absolute start block of the decay - function decay(V3Decay memory curve, uint256 startAmount, uint256 decayStartBlock) + function decay(NonlinearDutchDecay memory curve, uint256 startAmount, uint256 decayStartBlock) internal view returns (uint256 decayedAmount) { // mismatch of relativeAmounts and relativeBlocks - if(curve.relativeAmounts.length > 16) { + if (curve.relativeAmounts.length > 16) { revert InvalidDecayCurve(); } @@ -38,15 +37,17 @@ library V3DutchDecayLib { uint16 blockDelta = uint16(block.number - decayStartBlock); // Special case for when we need to use the decayStartBlock (0) if (relativeBlocks.getElement(0) > blockDelta) { - return linearDecay(0, relativeBlocks.getElement(0), blockDelta, startAmount, startAmount.sub(curve.relativeAmounts[0])); + return linearDecay( + 0, relativeBlocks.getElement(0), blockDelta, startAmount, startAmount.sub(curve.relativeAmounts[0]) + ); } // the current pos is within or after the curve - uint16 prev; - uint16 next; - (prev, next) = locateCurvePosition(curve, blockDelta); + (uint16 prev, uint16 next) = locateCurvePosition(curve, blockDelta); uint256 lastAmount = startAmount.sub(curve.relativeAmounts[prev]); uint256 nextAmount = startAmount.sub(curve.relativeAmounts[next]); - return linearDecay(relativeBlocks.getElement(prev), relativeBlocks.getElement(next), blockDelta, lastAmount, nextAmount); + return linearDecay( + relativeBlocks.getElement(prev), relativeBlocks.getElement(next), blockDelta, lastAmount, nextAmount + ); } /// @notice Locates the current position on the curve using a binary search @@ -54,15 +55,13 @@ library V3DutchDecayLib { /// @param currentRelativeBlock The current relative position /// @return prev The relative block before the current position /// @return next The relative block after the current position - function locateCurvePosition(V3Decay memory curve, uint16 currentRelativeBlock) + function locateCurvePosition(NonlinearDutchDecay memory curve, uint16 currentRelativeBlock) internal pure returns (uint16 prev, uint16 next) { Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); - prev = 0; - next = 0; - while(next < curve.relativeAmounts.length) { + while (next < curve.relativeAmounts.length) { if (relativeBlocks.getElement(next) >= currentRelativeBlock) { return (prev, next); } @@ -108,7 +107,7 @@ library V3DutchDecayLib { view returns (OutputToken memory result) { - uint256 decayedOutput = V3DutchDecayLib.decay(output.curve, output.startAmount, decayStartBlock); + uint256 decayedOutput = NonlinearDutchDecayLib.decay(output.curve, output.startAmount, decayStartBlock); result = OutputToken(output.token, decayedOutput, output.recipient); } @@ -139,7 +138,7 @@ library V3DutchDecayLib { view returns (InputToken memory result) { - uint256 decayedInput = V3DutchDecayLib.decay(input.curve, input.startAmount, decayStartBlock); + uint256 decayedInput = NonlinearDutchDecayLib.decay(input.curve, input.startAmount, decayStartBlock); result = InputToken(input.token, decayedInput, input.maxAmount); } } diff --git a/src/lib/V3DutchOrderLib.sol b/src/lib/V3DutchOrderLib.sol index f539d2ee..04783433 100644 --- a/src/lib/V3DutchOrderLib.sol +++ b/src/lib/V3DutchOrderLib.sol @@ -36,7 +36,7 @@ struct V3DutchOrder { /// @dev The changes in tokens (positive or negative) to subtract from the start amount /// @dev The relativeBlocks should be strictly increasing -struct V3Decay { +struct NonlinearDutchDecay { // 16 uint16 values packed // Can represent curves with points 2^16 blocks into the future uint256 relativeBlocks; @@ -50,7 +50,7 @@ struct V3DutchInput { // The amount of tokens at the starting block uint256 startAmount; // The amount of tokens at the each future block - V3Decay curve; + NonlinearDutchDecay curve; // The max amount of the curve uint256 maxAmount; } @@ -62,7 +62,7 @@ struct V3DutchOutput { // The amount of tokens at the start of the time period uint256 startAmount; // The amount of tokens at the each future block - V3Decay curve; + NonlinearDutchDecay curve; // The address who must receive the tokens to satisfy the order address recipient; } @@ -79,11 +79,11 @@ library V3DutchOrderLib { "V3DutchOutput[] baseOutputs)" ); bytes internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE = abi.encodePacked( - "V3DutchOutput(", "address token,", "uint256 startAmount,", "V3Decay curve,", "address recipient)" + "V3DutchOutput(", "address token,", "uint256 startAmount,", "NonlinearDutchDecay curve,", "address recipient)" ); bytes32 internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH = keccak256(NON_LINEAR_DUTCH_OUTPUT_TYPE); bytes internal constant NON_LINEAR_DECAY_TYPE = - abi.encodePacked("V3Decay(", "uint256 relativeBlocks,", "int256[] relativeAmounts)"); + 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( @@ -103,7 +103,7 @@ library V3DutchOrderLib { ) ); - function hash(V3Decay memory curve) internal pure returns (bytes32) { + 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)) diff --git a/src/reactors/V3DutchOrderReactor.sol b/src/reactors/V3DutchOrderReactor.sol index 099117c4..82e02339 100644 --- a/src/reactors/V3DutchOrderReactor.sol +++ b/src/reactors/V3DutchOrderReactor.sol @@ -5,14 +5,8 @@ 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 {V3DutchDecayLib} from "../lib/V3DutchDecayLib.sol"; -import { - V3DutchOrderLib, - V3DutchOrder, - CosignerData, - V3DutchOutput, - V3DutchInput -} from "../lib/V3DutchOrderLib.sol"; +import {NonlinearDutchDecayLib} from "../lib/NonlinearDutchDecayLib.sol"; +import {V3DutchOrderLib, V3DutchOrder, CosignerData, V3DutchOutput, V3DutchInput} from "../lib/V3DutchOrderLib.sol"; import {SignedOrder, ResolvedOrder} from "../base/ReactorStructs.sol"; /// @notice Reactor for non-linear dutch orders @@ -27,8 +21,8 @@ import {SignedOrder, ResolvedOrder} from "../base/ReactorStructs.sol"; contract V3DutchOrderReactor is BaseReactor { using Permit2Lib for ResolvedOrder; using V3DutchOrderLib for V3DutchOrder; - using V3DutchDecayLib for V3DutchOutput[]; - using V3DutchDecayLib for V3DutchInput; + using NonlinearDutchDecayLib for V3DutchOutput[]; + using NonlinearDutchDecayLib for V3DutchInput; using ExclusivityLib for ResolvedOrder; /// @notice thrown when an order's deadline is passed diff --git a/src/types/Uint16Array.sol b/src/types/Uint16Array.sol index d871fccd..b6624eae 100644 --- a/src/types/Uint16Array.sol +++ b/src/types/Uint16Array.sol @@ -3,7 +3,9 @@ pragma solidity ^0.8.0; /// @dev An uint16 array of max 16 values packed into a single uint256 type Uint16Array is uint256; + using Uint16ArrayLibrary for Uint16Array global; + error IndexOutOfBounds(); error InvalidArrLength(); @@ -41,4 +43,4 @@ library Uint16ArrayLibrary { return result; } } -} \ No newline at end of file +} diff --git a/test/lib/ExclusivityLib.t.sol b/test/lib/ExclusivityLib.t.sol index 6d96f8f0..c6cc0e18 100644 --- a/test/lib/ExclusivityLib.t.sol +++ b/test/lib/ExclusivityLib.t.sol @@ -31,9 +31,7 @@ contract ExclusivityLibTest is Test { assertEq(exclusivity.hasFillingRights(exclusive, block.timestamp + 1, block.timestamp), true); } - function testExclusivityFail(address caller, address exclusive, uint256 nowTime, uint256 exclusiveEnd) - public - { + function testExclusivityFail(address caller, address exclusive, uint256 nowTime, uint256 exclusiveEnd) public { vm.assume(nowTime <= exclusiveEnd); vm.assume(caller != exclusive); vm.assume(exclusive != address(0)); @@ -76,7 +74,9 @@ contract ExclusivityLibTest is Test { assertEq(handled.outputs[0].recipient, recipient); } - function testHandleExclusiveOverrideTimestampPassNoExclusivity(address caller, uint256 overrideAmt, uint128 amount) public { + function testHandleExclusiveOverrideTimestampPassNoExclusivity(address caller, uint256 overrideAmt, uint128 amount) + public + { vm.assume(overrideAmt < 10000); ResolvedOrder memory order; order.outputs = OutputsBuilder.single(token1, amount, recipient); @@ -88,7 +88,9 @@ contract ExclusivityLibTest is Test { assertEq(handled.outputs[0].recipient, recipient); } - function testHandleExclusiveOverrideBlockPassNoExclusivity(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); @@ -206,9 +208,12 @@ contract ExclusivityLibTest is Test { assertEq(handled.outputs[0].recipient, recipient); } - function testHandleExclusiveOverrideTimestampApplied(address caller, address exclusive, uint256 overrideAmt, uint128 amount) - public - { + 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); @@ -222,9 +227,12 @@ contract ExclusivityLibTest is Test { assertEq(handled.outputs[0].recipient, recipient); } - function testHandleExclusiveOverrideBlockApplied(address caller, address exclusive, uint256 overrideAmt, uint128 amount) - public - { + 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); diff --git a/test/lib/MathExt.t.sol b/test/lib/MathExt.t.sol index c5134920..73cf4463 100644 --- a/test/lib/MathExt.t.sol +++ b/test/lib/MathExt.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import {Test} from "forge-std/Test.sol"; -import {V3DutchDecayLib} from "../../src/lib/V3DutchDecayLib.sol"; -import {V3DutchOutput, V3DutchInput, V3Decay} from "../../src/lib/V3DutchOrderLib.sol"; +import {NonlinearDutchDecayLib} from "../../src/lib/NonlinearDutchDecayLib.sol"; +import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../../src/lib/V3DutchOrderLib.sol"; import {sub, NegativeUint} from "../../src/lib/MathExt.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol new file mode 100644 index 00000000..d87587ee --- /dev/null +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -0,0 +1,431 @@ +// 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 {NonlinearDutchDecayLib, InvalidDecayCurve} 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"; +import {NegativeUint} from "../../src/lib/MathExt.sol"; + +/// @notice mock contract to test NonlinearDutchDecayLib functionality +contract MockNonlinearDutchDecayLibContract { + function decay(NonlinearDutchDecay memory curve, uint256 startAmount, uint256 decayStartBlock) public view { + NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + } +} + +contract NonlinearDutchDecayLibTest is Test, GasSnapshot { + MockNonlinearDutchDecayLibContract mockNonlinearDutchDecayLibContract = new MockNonlinearDutchDecayLibContract(); + + function testLocateCurvePositionSingle() public { + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(1, 0); + + snapStart("V3-LocateCurvePositionSingle"); + (uint16 prev, uint16 next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 1); + assertEq(prev, 0); + assertEq(next, 0); + + (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 2); + assertEq(prev, 0); + assertEq(next, 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 prev, uint16 next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 50); + assertEq(prev, 0); + assertEq(next, 0); + + (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 100); + assertEq(prev, 0); + assertEq(next, 0); + + (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 150); + assertEq(prev, 0); + assertEq(next, 1); + + (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 200); + assertEq(prev, 0); + assertEq(next, 1); + + (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 250); + assertEq(prev, 1); + assertEq(next, 2); + + (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 300); + assertEq(prev, 1); + assertEq(next, 2); + + (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 350); + assertEq(prev, 2); + assertEq(next, 2); + snapEnd(); + } + + function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { + // Empty curve + snapStart("V3-DutchDecayNoDecay"); + assertEq(NonlinearDutchDecayLib.decay(CurveBuilder.emptyCurve(), startAmount, decayStartBlock), startAmount); + + // Single value with 0 amount change + assertEq( + NonlinearDutchDecayLib.decay(CurveBuilder.singlePointCurve(1, 0), startAmount, decayStartBlock), 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); + + vm.roll(80); + // before decayStartBlock + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 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), startAmount); + + vm.roll(80); + // before decayStartBlock + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 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.5 ether); + + vm.roll(180); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); + + vm.roll(110); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + + vm.roll(190); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 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.5 ether); + + vm.roll(180); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.2 ether); + + vm.roll(110); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + + vm.roll(190); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 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), 2 ether); + + vm.warp(250); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 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); + + vm.warp(250); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + snapEnd(); + } + + function testDutchDecayBounded( + uint256 startAmount, + uint256 decayAmount, + uint256 decayStartBlock, + uint16 decayDuration + ) public { + vm.assume(decayAmount > 0); + vm.assume(decayAmount < 2 ** 255 - 1); + vm.assume(startAmount <= UINT256_MAX - decayAmount); + vm.assume(decayDuration > 0); + + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(decayDuration, 0 - int256(decayAmount)); + snapStart("V3-DutchDecayBounded"); + uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + assertGe(decayed, startAmount); + assertLe(decayed, startAmount + decayAmount); + 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); + vm.assume(decayDuration > 0); + + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(decayDuration, int256(decayAmount)); + snapStart("V3-DutchDecayNegative"); + uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + 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), 1 ether); + + vm.roll(150); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); + + vm.roll(200); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); + + vm.roll(210); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + + vm.roll(290); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + + vm.roll(300); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + + vm.roll(350); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.5 ether); + + vm.roll(400); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); + + vm.roll(500); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 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); + + vm.roll(150); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.05 ether); // halfway between 100 (1 ether) and 200 (1.1 ether) + + vm.roll(200); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); // 1 + 0.1 ether + + vm.roll(250); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.15 ether); // halfway between 200 (1.1 ether) and 300 (1.2 ether) + + vm.roll(300); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.2 ether); // 1 + 0.2 ether + + vm.roll(350); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.25 ether); // halfway between 300 (1.2 ether) and 400 (1.3 ether) + + vm.roll(400); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.3 ether); // 1 + 0.3 ether + + vm.roll(450); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.35 ether); // halfway between 400 (1.3 ether) and 500 (1.4 ether) + + vm.roll(500); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.4 ether); // 1 + 0.4 ether + + vm.roll(600); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); // 1 + 0.5 ether + + vm.roll(700); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.6 ether); // 1 + 0.6 ether + + vm.roll(800); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.7 ether); // 1 + 0.7 ether + + vm.roll(900); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); // 1 + 0.8 ether + + vm.roll(1000); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); // 1 + 0.9 ether + + vm.roll(1100); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); // 1 + 1 ether + + vm.roll(1200); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); // 1 + 0.9 ether + + vm.roll(1300); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); // 1 + 0.8 ether + + vm.roll(1400); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.7 ether); // 1 + 0.7 ether + + vm.roll(1500); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.6 ether); // 1 + 0.6 ether + + vm.roll(1600); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); // 1 + 0.5 ether + + vm.roll(1650); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 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.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); + vm.expectRevert(NegativeUint.selector); + mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + } + + function testDutchOverflowDecay() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + int256 decayAmount = -(2 ** 255 - 1); + NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); + vm.roll(150); + vm.expectRevert(); + mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + } + + function testDutchMismatchedDecay() public { + uint256 decayStartBlock = 100; + uint256 startAmount = 1 ether; + NonlinearDutchDecay memory curve = + CurveBuilder.multiPointCurve(ArrayBuilder.fillUint16(16, 1), ArrayBuilder.fillInt(17, 0)); + vm.roll(150); + vm.expectRevert(InvalidDecayCurve.selector); + mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + } +} diff --git a/test/lib/Uint16Array.t.sol b/test/lib/Uint16Array.t.sol index 44175742..e54a3d67 100644 --- a/test/lib/Uint16Array.t.sol +++ b/test/lib/Uint16Array.t.sol @@ -2,13 +2,12 @@ pragma solidity ^0.8.0; import {Test} from "forge-std/Test.sol"; -import {V3DutchDecayLib} from "../../src/lib/V3DutchDecayLib.sol"; -import {V3DutchOutput, V3DutchInput, V3Decay} from "../../src/lib/V3DutchOrderLib.sol"; +import {NonlinearDutchDecayLib} from "../../src/lib/NonlinearDutchDecayLib.sol"; +import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../../src/lib/V3DutchOrderLib.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; import {Uint16Array, toUint16Array, InvalidArrLength, IndexOutOfBounds} from "../../src/types/Uint16Array.sol"; contract Uint16ArrayTest is Test { - function testGetElement(uint16 value, uint16 length) public { vm.assume(length <= 16); Uint16Array packedArr = toUint16Array(ArrayBuilder.fillUint16(length, value)); diff --git a/test/lib/V3DutchDecayLib.t.sol b/test/lib/V3DutchDecayLib.t.sol deleted file mode 100644 index ee7fee7d..00000000 --- a/test/lib/V3DutchDecayLib.t.sol +++ /dev/null @@ -1,479 +0,0 @@ -// 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 {V3DutchDecayLib, InvalidDecayCurve} from "../../src/lib/V3DutchDecayLib.sol"; -import {V3DutchOutput, V3DutchInput, V3Decay} from "../../src/lib/V3DutchOrderLib.sol"; -import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol"; -import {ArrayBuilder} from "../util/ArrayBuilder.sol"; -import {NegativeUint} from "../../src/lib/MathExt.sol"; - -/// @notice mock contract to test V3DutchDecayLib functionality -contract MockV3DutchDecayLibContract { - function decay(V3Decay memory curve, uint256 startAmount, uint256 decayStartBlock) public view { - V3DutchDecayLib.decay(curve, startAmount, decayStartBlock); - } -} - -contract V3DutchDecayLibTest is Test, GasSnapshot { - MockV3DutchDecayLibContract mockV3DutchDecayLibContract = new MockV3DutchDecayLibContract(); - - function testLocateCurvePositionSingle() public { - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), - relativeAmounts: ArrayBuilder.fillInt(1, 0) - }); - snapStart("V3-LocateCurvePositionSingle"); - uint16 prev; - uint16 next; - (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 1); - assertEq(prev, 0); - assertEq(next, 0); - - (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 2); - assertEq(prev, 0); - assertEq(next, 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 - V3Decay memory curve = - V3Decay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); - - snapStart("V3-LocateCurvePositionMulti"); - uint16 prev; - uint16 next; - // currentRelativeBlock shouldn't be less than the first block - // but testing behavior anyways - (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 50); - assertEq(prev, 0); - assertEq(next, 0); - - (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 100); - assertEq(prev, 0); - assertEq(next, 0); - - (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 150); - assertEq(prev, 0); - assertEq(next, 1); - - (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 200); - assertEq(prev, 0); - assertEq(next, 1); - - (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 250); - assertEq(prev, 1); - assertEq(next, 2); - - (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 300); - assertEq(prev, 1); - assertEq(next, 2); - - (prev, next) = V3DutchDecayLib.locateCurvePosition(curve, 350); - assertEq(prev, 2); - assertEq(next, 2); - snapEnd(); - } - - function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { - // Empty curve - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(0, 0)), - relativeAmounts: ArrayBuilder.fillInt(0, 0) - }); - snapStart("V3-DutchDecayNoDecay"); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); - - // Single value with 0 amount change - curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), - relativeAmounts: ArrayBuilder.fillInt(1, 0) - }); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); - snapEnd(); - } - - function testDutchDecayNoDecayYet() public { - uint256 decayStartBlock = 200; - uint256 startAmount = 1 ether; - int256 decayAmount = -1 ether; - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), - relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) - }); - snapStart("V3-DutchDecayNoDecayYet"); - vm.roll(100); - // at decayStartBlock - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); - - vm.roll(80); - // before decayStartBlock - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); - snapEnd(); - } - - function testDutchDecayNoDecayYetNegative() public { - uint256 decayStartBlock = 200; - uint256 startAmount = 1 ether; - int256 decayAmount = 1 ether; - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)), - relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) - }); - snapStart("V3-DutchDecayNoDecayYetNegative"); - vm.roll(100); - // at decayStartBlock - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); - - vm.roll(80); - // before decayStartBlock - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); - snapEnd(); - } - - function testDutchDecay() public { - uint256 decayStartBlock = 100; - uint256 startAmount = 1 ether; - int256 decayAmount = -1 ether; - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) - }); - snapStart("V3-DutchDecay"); - vm.roll(150); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); - - vm.roll(180); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); - - vm.roll(110); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); - - vm.roll(190); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); - snapEnd(); - } - - function testDutchDecayNegative() public { - uint256 decayStartBlock = 100; - uint256 startAmount = 2 ether; - int256 decayAmount = 1 ether; - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) - }); - snapStart("V3-DutchDecayNegative"); - vm.roll(150); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); - - vm.roll(180); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.2 ether); - - vm.roll(110); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); - - vm.roll(190); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); - snapEnd(); - } - - function testDutchDecayFullyDecayed() public { - uint256 decayStartBlock = 100; - uint256 startAmount = 1 ether; - int256 decayAmount = -1 ether; - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) - }); - snapStart("V3-DutchDecayFullyDecayed"); - vm.roll(200); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); - - vm.warp(250); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); - snapEnd(); - } - - function testDutchDecayFullyDecayedNegative() public { - uint256 decayStartBlock = 100; - uint256 startAmount = 2 ether; - int256 decayAmount = 1 ether; - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) - }); - snapStart("V3-DutchDecayFullyDecayedNegative"); - vm.roll(200); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); - - vm.warp(250); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); - snapEnd(); - } - - function testDutchDecayBounded( - uint256 startAmount, - uint256 decayAmount, - uint256 decayStartBlock, - uint16 decayDuration - ) public { - vm.assume(decayAmount > 0); - vm.assume(decayAmount < 2 ** 255 - 1); - vm.assume(startAmount <= UINT256_MAX - decayAmount); - vm.assume(decayDuration > 0); - - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, decayDuration)), - relativeAmounts: ArrayBuilder.fillInt(1, 0 - int256(decayAmount)) - }); - snapStart("V3-DutchDecayBounded"); - uint256 decayed = V3DutchDecayLib.decay(curve, startAmount, decayStartBlock); - assertGe(decayed, startAmount); - assertLe(decayed, startAmount + decayAmount); - 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); - vm.assume(decayDuration > 0); - - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, decayDuration)), - relativeAmounts: ArrayBuilder.fillInt(1, int256(decayAmount)) - }); - snapStart("V3-DutchDecayNegative"); - uint256 decayed = V3DutchDecayLib.decay(curve, startAmount, decayStartBlock); - 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 - V3Decay memory curve = - V3Decay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); - snapStart("V3-MultiPointDutchDecay"); - vm.roll(50); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); - - vm.roll(150); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); - - vm.roll(200); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); - - vm.roll(210); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); - - vm.roll(290); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); - - vm.roll(300); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); - - vm.roll(350); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.5 ether); - - vm.roll(400); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); - - vm.roll(500); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 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; - - V3Decay memory curve = - V3Decay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); - snapStart("V3-ExtendedMultiPointDutchDecay"); - - vm.roll(50); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); - - vm.roll(150); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.05 ether); // halfway between 100 (1 ether) and 200 (1.1 ether) - - vm.roll(200); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); // 1 + 0.1 ether - - vm.roll(250); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.15 ether); // halfway between 200 (1.1 ether) and 300 (1.2 ether) - - vm.roll(300); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.2 ether); // 1 + 0.2 ether - - vm.roll(350); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.25 ether); // halfway between 300 (1.2 ether) and 400 (1.3 ether) - - vm.roll(400); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.3 ether); // 1 + 0.3 ether - - vm.roll(450); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.35 ether); // halfway between 400 (1.3 ether) and 500 (1.4 ether) - - vm.roll(500); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.4 ether); // 1 + 0.4 ether - - vm.roll(600); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); // 1 + 0.5 ether - - vm.roll(700); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.6 ether); // 1 + 0.6 ether - - vm.roll(800); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.7 ether); // 1 + 0.7 ether - - vm.roll(900); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); // 1 + 0.8 ether - - vm.roll(1000); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); // 1 + 0.9 ether - - vm.roll(1100); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); // 1 + 1 ether - - vm.roll(1200); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); // 1 + 0.9 ether - - vm.roll(1300); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); // 1 + 0.8 ether - - vm.roll(1400); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.7 ether); // 1 + 0.7 ether - - vm.roll(1500); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.6 ether); // 1 + 0.6 ether - - vm.roll(1600); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); // 1 + 0.5 ether - - vm.roll(1650); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 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 - V3Decay memory curve = - V3Decay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts}); - vm.roll(350); - assertEq(V3DutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.25 ether); - } - - function testDutchDecayToNegative() public { - uint256 decayStartBlock = 100; - uint256 startAmount = 1 ether; - int256 decayAmount = 2 ether; - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) - }); - vm.roll(150); - vm.expectRevert(NegativeUint.selector); - mockV3DutchDecayLibContract.decay(curve, startAmount, decayStartBlock); - } - - function testDutchOverflowDecay() public { - uint256 decayStartBlock = 100; - uint256 startAmount = 1 ether; - int256 decayAmount = -(2 ** 255 - 1); - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 100)), - relativeAmounts: ArrayBuilder.fillInt(1, decayAmount) - }); - vm.roll(150); - vm.expectRevert(); - mockV3DutchDecayLibContract.decay(curve, startAmount, decayStartBlock); - } - - function testDutchMismatchedDecay() public { - uint256 decayStartBlock = 100; - uint256 startAmount = 1 ether; - V3Decay memory curve = V3Decay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(16, 1)), - relativeAmounts: ArrayBuilder.fillInt(17, 0) - }); - vm.roll(150); - vm.expectRevert(InvalidDecayCurve.selector); - mockV3DutchDecayLibContract.decay(curve, startAmount, decayStartBlock); - } -} diff --git a/test/reactors/V3DutchOrderReactor.t.sol b/test/reactors/V3DutchOrderReactor.t.sol new file mode 100644 index 00000000..26bc0557 --- /dev/null +++ b/test/reactors/V3DutchOrderReactor.t.sol @@ -0,0 +1,460 @@ +// 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"; + +contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { + using OrderInfoBuilder for OrderInfo; + using V3DutchOrderLib for V3DutchOrder; + + 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 + }); + } + + 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 + ), + baseOutputs: outputs, + cosignerData: cosignerData, + cosignature: bytes("") + }); + orderHash = order.hash(); + order.cosignature = cosignOrder(orderHash, cosignerData); + return (SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), orderHash); + } + + function testWrongCosigner() 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), + baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 1 ether, swapper), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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 testInvalidCosignature() 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), + baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 1 ether, swapper), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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 testInputOverrideWorse() 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), + baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 1 ether, swapper), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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 testOutputOverrideWorse() 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), + baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 0.8 ether, swapper), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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 testOutputOverrideWrongLength() 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), + baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 0.8 ether, swapper), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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 testOverrideInput() 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), + baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), outputAmount, outputAmount, swapper), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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 testOverrideOutput() 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), + baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 0.9 ether, swapper), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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 testStrictExclusivityInvalidCaller() 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), + baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 0.9 ether, swapper), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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 testStrictExclusivityValidCaller() 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), + baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), outputAmount, 0.9 ether, swapper), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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 testExclusiveOverrideInvalidCallerCosignedAmountOutput() 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), + baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 0.9 ether, swapper), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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 testExclusiveOverrideInvalidCallerNoCosignedAmountOutput() 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), + baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), outputAmount, 0.9 ether, swapper), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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); + } + + // function testExecuteInputAndOutputDecay() public { + // uint256 inputAmount = 1 ether; + // uint256 outputAmount = 1 ether; + // uint256 startTime = block.timestamp; + // uint256 deadline = startTime + 1000; + // // Seed both swapper and fillContract with enough tokens (important for dutch order) + // tokenIn.mint(address(swapper), uint256(inputAmount) * 100); + // tokenOut.mint(address(fillContract), uint256(outputAmount) * 100); + // tokenIn.forceApprove(swapper, address(permit2), inputAmount); + + // SignedOrder memory order = generateOrder( + // TestDutchOrderSpec({ + // currentTime: startTime, + // startTime: startTime, + // endTime: deadline, + // deadline: deadline, + // input: V3DutchInput(tokenIn, inputAmount, inputAmount * 110 / 100), + // outputs: OutputsBuilder.singleV3Dutch(tokenOut, outputAmount, outputAmount * 90 / 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.warp(startTime + 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 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/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..f0796be5 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,36 @@ library OutputsBuilder { } return result; } + + function singleV3Dutch(address token, uint256 amount, uint256 endAmount, address recipient) + internal + pure + returns (V3DutchOutput[] memory) + { + int256 delta = int256(amount - endAmount); + NonlinearDutchDecay memory curve = NonlinearDutchDecay({ + relativeBlocks: toUint256(ArrayBuilder.fillUint16(0, 0)), + relativeAmounts: ArrayBuilder.fillInt(1, delta) + }); + V3DutchOutput[] memory result = new V3DutchOutput[](1); + result[0] = V3DutchOutput(token, amount, curve, recipient); + return result; + } + + function multipleV3Dutch(address token, uint256[] memory amounts, uint256 endAmount, address recipient) + internal + pure + returns (V3DutchOutput[] memory) + { + V3DutchOutput[] memory result = new V3DutchOutput[](amounts.length); + for (uint256 i = 0; i < amounts.length; i++) { + int256 delta = int256(amounts[i] - endAmount); + NonlinearDutchDecay memory curve = NonlinearDutchDecay({ + relativeBlocks: toUint256(ArrayBuilder.fillUint16(0, 0)), + relativeAmounts: ArrayBuilder.fillInt(1, delta) + }); + result[i] = V3DutchOutput(token, amounts[i], curve, recipient); + } + 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 bb7252d6..ee6f860d 100644 --- a/test/util/mock/MockExclusivityLib.sol +++ b/test/util/mock/MockExclusivityLib.sol @@ -14,6 +14,7 @@ contract MockExclusivityLib { ExclusivityLib.handleExclusiveOverrideTimestamp(order, exclusive, exclusivityEnd, exclusivityOverrideBps); return order; } + function handleExclusiveOverrideBlock( ResolvedOrder memory order, address exclusive, From 13ca0a920b806a62bdfb1a0e445a2db69fb6d685 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Tue, 3 Sep 2024 17:01:52 -0400 Subject: [PATCH 16/24] More reactor tests, address PR feedback --- ...V3DutchOrder-BaseExecuteSingleWithFee.snap | 2 +- .../Base-V3DutchOrder-ExecuteBatch.snap | 2 +- ...utchOrder-ExecuteBatchMultipleOutputs.snap | 2 +- ...teBatchMultipleOutputsDifferentTokens.snap | 2 +- ...V3DutchOrder-ExecuteBatchNativeOutput.snap | 2 +- .../Base-V3DutchOrder-ExecuteSingle.snap | 2 +- ...3DutchOrder-ExecuteSingleNativeOutput.snap | 2 +- ...-V3DutchOrder-ExecuteSingleValidation.snap | 2 +- .../Base-V3DutchOrder-RevertInvalidNonce.snap | 2 +- .forge-snapshots/V3-DutchDecayBounded.snap | 2 +- .../V3-DutchDecayFullyDecayed.snap | 2 +- .../V3-DutchDecayFullyDecayedNegative.snap | 2 +- .../V3-ExtendedMultiPointDutchDecay.snap | 2 +- .../V3-LocateCurvePositionMulti.snap | 2 +- .../V3-LocateCurvePositionSingle.snap | 2 +- .forge-snapshots/V3-MultiPointDutchDecay.snap | 2 +- src/lib/NonlinearDutchDecayLib.sol | 7 +- src/lib/V3DutchOrderLib.sol | 4 +- src/reactors/V3DutchOrderReactor.sol | 9 +- src/types/Uint16Array.sol | 2 - test/lib/NonLinearDutchDecayLib.t.sol | 1 - test/lib/Uint16Array.t.sol | 4 +- test/reactors/V3DutchOrderReactor.t.sol | 687 +++++++++++++++--- test/util/OutputsBuilder.sol | 18 +- 24 files changed, 622 insertions(+), 142 deletions(-) diff --git a/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap index 2825e932..f096fc15 100644 --- a/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -192981 \ No newline at end of file +192980 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap index 2ab57fb9..6f25147c 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -219434 \ No newline at end of file +219432 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap index 3c001253..173e14c0 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -231444 \ No newline at end of file +231439 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index eef5a09a..921318a6 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -287371 \ No newline at end of file +287363 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap index b4dbf0f4..b67cac65 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -212953 \ No newline at end of file +212951 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap index d1ade6b4..8ae6c44e 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -159369 \ No newline at end of file +159368 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap index eab5e06d..9507876d 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -144927 \ No newline at end of file +144926 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap index d23dabd5..6523b747 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -168675 \ No newline at end of file +168674 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap b/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap index 0e0324bc..4159e5c8 100644 --- a/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap +++ b/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap @@ -1 +1 @@ -37963 \ No newline at end of file +37962 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayBounded.snap b/.forge-snapshots/V3-DutchDecayBounded.snap index db604d62..3af7d09a 100644 --- a/.forge-snapshots/V3-DutchDecayBounded.snap +++ b/.forge-snapshots/V3-DutchDecayBounded.snap @@ -1 +1 @@ -5778 \ No newline at end of file +1070 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap index a901a15c..55ffc0c6 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap @@ -1 +1 @@ -14782 \ No newline at end of file +14810 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap index 9ce7dd3d..718fee6a 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap @@ -1 +1 @@ -14634 \ No newline at end of file +14662 \ No newline at end of file diff --git a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap index e4368d80..fa08fe03 100644 --- a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap @@ -1 +1 @@ -219186 \ No newline at end of file +208112 \ No newline at end of file diff --git a/.forge-snapshots/V3-LocateCurvePositionMulti.snap b/.forge-snapshots/V3-LocateCurvePositionMulti.snap index 6c884d81..06648cad 100644 --- a/.forge-snapshots/V3-LocateCurvePositionMulti.snap +++ b/.forge-snapshots/V3-LocateCurvePositionMulti.snap @@ -1 +1 @@ -24798 \ No newline at end of file +24086 \ No newline at end of file diff --git a/.forge-snapshots/V3-LocateCurvePositionSingle.snap b/.forge-snapshots/V3-LocateCurvePositionSingle.snap index 111a659b..ca44913f 100644 --- a/.forge-snapshots/V3-LocateCurvePositionSingle.snap +++ b/.forge-snapshots/V3-LocateCurvePositionSingle.snap @@ -1 +1 @@ -8820 \ No newline at end of file +8758 \ No newline at end of file diff --git a/.forge-snapshots/V3-MultiPointDutchDecay.snap b/.forge-snapshots/V3-MultiPointDutchDecay.snap index a605cd33..a507085d 100644 --- a/.forge-snapshots/V3-MultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-MultiPointDutchDecay.snap @@ -1 +1 @@ -51976 \ No newline at end of file +51174 \ No newline at end of file diff --git a/src/lib/NonlinearDutchDecayLib.sol b/src/lib/NonlinearDutchDecayLib.sol index b8927759..9784081e 100644 --- a/src/lib/NonlinearDutchDecayLib.sol +++ b/src/lib/NonlinearDutchDecayLib.sol @@ -5,7 +5,7 @@ import {OutputToken, InputToken} from "../base/ReactorStructs.sol"; import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../lib/V3DutchOrderLib.sol"; import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; import {sub} from "./MathExt.sol"; -import {Uint16Array, fromUnderlying} from "../types/Uint16Array.sol"; +import {Uint16ArrayLibrary, Uint16Array, fromUnderlying} from "../types/Uint16Array.sol"; /// @notice thrown when the decay curve is invalid error InvalidDecayCurve(); @@ -14,6 +14,7 @@ error InvalidDecayCurve(); library NonlinearDutchDecayLib { using FixedPointMathLib for uint256; using {sub} for uint256; + using Uint16ArrayLibrary for Uint16Array; /// @notice locates the current position on the curve and calculates the decay /// @param curve The curve to search @@ -61,12 +62,12 @@ library NonlinearDutchDecayLib { returns (uint16 prev, uint16 next) { Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); - while (next < curve.relativeAmounts.length) { + uint16 curveLength = uint16(curve.relativeAmounts.length); + for (; next < curveLength; next++) { if (relativeBlocks.getElement(next) >= currentRelativeBlock) { return (prev, next); } prev = next; - next++; } return (next - 1, next - 1); } diff --git a/src/lib/V3DutchOrderLib.sol b/src/lib/V3DutchOrderLib.sol index 04783433..60a6145d 100644 --- a/src/lib/V3DutchOrderLib.sol +++ b/src/lib/V3DutchOrderLib.sol @@ -143,8 +143,8 @@ library V3DutchOrderLib { function hash(V3DutchOutput[] memory outputs) internal pure returns (bytes32) { unchecked { bytes memory packedHashes = new bytes(32 * outputs.length); - - for (uint256 i = 0; i < outputs.length; i++) { + 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) diff --git a/src/reactors/V3DutchOrderReactor.sol b/src/reactors/V3DutchOrderReactor.sol index 82e02339..465acec1 100644 --- a/src/reactors/V3DutchOrderReactor.sol +++ b/src/reactors/V3DutchOrderReactor.sol @@ -51,9 +51,6 @@ contract V3DutchOrderReactor is BaseReactor { // hash the order _before_ overriding amounts, as this is the hash the user would have signed bytes32 orderHash = order.hash(); - if (order.info.deadline < block.timestamp) { - revert DeadlineReached(); - } _validateOrder(orderHash, order); _updateWithCosignerAmounts(order); @@ -108,8 +105,12 @@ contract V3DutchOrderReactor is BaseReactor { /// @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 pure { + function _validateOrder(bytes32 orderHash, V3DutchOrder memory order) internal view { + if (order.info.deadline < block.timestamp) { + revert DeadlineReached(); + } (bytes32 r, bytes32 s) = abi.decode(order.cosignature, (bytes32, bytes32)); uint8 v = uint8(order.cosignature[64]); // cosigner signs over (orderHash || cosignerData) diff --git a/src/types/Uint16Array.sol b/src/types/Uint16Array.sol index b6624eae..a940d33a 100644 --- a/src/types/Uint16Array.sol +++ b/src/types/Uint16Array.sol @@ -4,8 +4,6 @@ pragma solidity ^0.8.0; /// @dev An uint16 array of max 16 values packed into a single uint256 type Uint16Array is uint256; -using Uint16ArrayLibrary for Uint16Array global; - error IndexOutOfBounds(); error InvalidArrLength(); diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index d87587ee..ed9d536d 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -200,7 +200,6 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { vm.assume(decayAmount > 0); vm.assume(decayAmount < 2 ** 255 - 1); vm.assume(startAmount <= UINT256_MAX - decayAmount); - vm.assume(decayDuration > 0); NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(decayDuration, 0 - int256(decayAmount)); snapStart("V3-DutchDecayBounded"); diff --git a/test/lib/Uint16Array.t.sol b/test/lib/Uint16Array.t.sol index e54a3d67..2d0ede85 100644 --- a/test/lib/Uint16Array.t.sol +++ b/test/lib/Uint16Array.t.sol @@ -5,9 +5,11 @@ 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 {Uint16Array, toUint16Array, InvalidArrLength, IndexOutOfBounds} from "../../src/types/Uint16Array.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)); diff --git a/test/reactors/V3DutchOrderReactor.t.sol b/test/reactors/V3DutchOrderReactor.t.sol index 26bc0557..77d2c2e3 100644 --- a/test/reactors/V3DutchOrderReactor.t.sol +++ b/test/reactors/V3DutchOrderReactor.t.sol @@ -27,10 +27,19 @@ 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 {sub} from "../../src/lib/MathExt.sol"; contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { using OrderInfoBuilder for OrderInfo; - using V3DutchOrderLib for V3DutchOrder; + using V3DutchOrderLib for V3DutchOrder; + OrderQuoter quoter; + using {sub} for uint256; + + constructor() { + quoter = new OrderQuoter(); + } uint256 constant cosignerPrivateKey = 0x99999999; @@ -88,57 +97,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { return (SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), orderHash); } - function testWrongCosigner() 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), - baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 1 ether, swapper), - cosignerData: cosignerData, - cosignature: bytes("") - }); - 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 testInvalidCosignature() 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), - baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 1 ether, swapper), - cosignerData: cosignerData, - cosignature: bytes("") - }); - 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); - } + /* Cosigner tests */ - function testInputOverrideWorse() public { + function testV3InputOverrideWorse() public { CosignerData memory cosignerData = CosignerData({ decayStartBlock: block.number, exclusiveFiller: address(0), @@ -152,7 +113,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, 0.8 ether, CurveBuilder.emptyCurve(), 1 ether), - baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 1 ether, swapper), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1 ether, + CurveBuilder.singlePointCurve(1, 0 ether), + swapper + ), cosignerData: cosignerData, cosignature: bytes("") }); @@ -163,7 +129,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { fillContract.execute(signedOrder); } - function testOutputOverrideWorse() public { + function testV3OutputOverrideWorse() public { CosignerData memory cosignerData = CosignerData({ decayStartBlock: block.number, exclusiveFiller: address(0), @@ -177,7 +143,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether), - baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 0.8 ether, swapper), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1 ether, + CurveBuilder.singlePointCurve(1, .2 ether), + swapper + ), cosignerData: cosignerData, cosignature: bytes("") }); @@ -188,7 +159,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { fillContract.execute(signedOrder); } - function testOutputOverrideWrongLength() public { + function testV3OutputOverrideWrongLength() public { CosignerData memory cosignerData = CosignerData({ decayStartBlock: block.number, exclusiveFiller: address(0), @@ -202,7 +173,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether), - baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 0.8 ether, swapper), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1 ether, + CurveBuilder.singlePointCurve(1, .2 ether), + swapper + ), cosignerData: cosignerData, cosignature: bytes("") }); @@ -213,7 +189,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { fillContract.execute(signedOrder); } - function testOverrideInput() public { + function testV3OverrideInput() public { uint256 outputAmount = 1 ether; uint256 overriddenInputAmount = 0.7 ether; tokenIn.mint(swapper, overriddenInputAmount); @@ -231,7 +207,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, 0.8 ether, CurveBuilder.emptyCurve(), 1 ether), - baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), outputAmount, outputAmount, swapper), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + outputAmount, + CurveBuilder.singlePointCurve(1, 0 ether), + swapper + ), cosignerData: cosignerData, cosignature: bytes("") }); @@ -248,7 +229,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { assertEq(tokenIn.balanceOf(address(fillContract)), overriddenInputAmount); } - function testOverrideOutput() public { + function testV3OverrideOutput() public { uint256 overriddenOutputAmount = 1.1 ether; uint256 inputAmount = 1 ether; tokenIn.mint(swapper, inputAmount); @@ -266,7 +247,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), - baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 0.9 ether, swapper), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1 ether, + CurveBuilder.singlePointCurve(1, .1 ether), + swapper + ), cosignerData: cosignerData, cosignature: bytes("") }); @@ -283,7 +269,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); } - function testStrictExclusivityInvalidCaller() public { + function testV3StrictExclusivityInvalidCaller() public { uint256 inputAmount = 1 ether; tokenIn.mint(swapper, inputAmount); tokenOut.mint(address(fillContract), inputAmount); @@ -300,7 +286,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), - baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 0.9 ether, swapper), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1 ether, + CurveBuilder.singlePointCurve(1, .1 ether), + swapper + ), cosignerData: cosignerData, cosignature: bytes("") }); @@ -311,7 +302,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { fillContract.execute(signedOrder); } - function testStrictExclusivityValidCaller() public { + function testV3StrictExclusivityValidCaller() public { uint256 inputAmount = 1 ether; uint256 outputAmount = 1 ether; tokenIn.mint(swapper, inputAmount); @@ -329,7 +320,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), - baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), outputAmount, 0.9 ether, swapper), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1 ether, + CurveBuilder.singlePointCurve(1, .1 ether), + swapper + ), cosignerData: cosignerData, cosignature: bytes("") }); @@ -347,7 +343,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); } - function testExclusiveOverrideInvalidCallerCosignedAmountOutput() public { + function testV3ExclusiveOverrideInvalidCallerCosignedAmountOutput() public { uint256 inputAmount = 1 ether; uint256 outputAmount = 1 ether; uint256 exclusivityOverrideBps = 10; @@ -366,7 +362,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), - baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1 ether, 0.9 ether, swapper), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1 ether, + CurveBuilder.singlePointCurve(1, .1 ether), + swapper + ), cosignerData: cosignerData, cosignature: bytes("") }); @@ -379,7 +380,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); } - function testExclusiveOverrideInvalidCallerNoCosignedAmountOutput() public { + function testV3ExclusiveOverrideInvalidCallerNoCosignedAmountOutput() public { uint256 inputAmount = 1 ether; uint256 outputAmount = 1 ether; uint256 exclusivityOverrideBps = 10; @@ -398,7 +399,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), - baseOutputs: OutputsBuilder.singleV3Dutch(address(tokenOut), outputAmount, 0.9 ether, swapper), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1 ether, + CurveBuilder.singlePointCurve(1, .1 ether), + swapper + ), cosignerData: cosignerData, cosignature: bytes("") }); @@ -412,37 +418,520 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); } - // function testExecuteInputAndOutputDecay() public { - // uint256 inputAmount = 1 ether; - // uint256 outputAmount = 1 ether; - // uint256 startTime = block.timestamp; - // uint256 deadline = startTime + 1000; - // // Seed both swapper and fillContract with enough tokens (important for dutch order) - // tokenIn.mint(address(swapper), uint256(inputAmount) * 100); - // tokenOut.mint(address(fillContract), uint256(outputAmount) * 100); - // tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - // SignedOrder memory order = generateOrder( - // TestDutchOrderSpec({ - // currentTime: startTime, - // startTime: startTime, - // endTime: deadline, - // deadline: deadline, - // input: V3DutchInput(tokenIn, inputAmount, inputAmount * 110 / 100), - // outputs: OutputsBuilder.singleV3Dutch(tokenOut, outputAmount, outputAmount * 90 / 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.warp(startTime + 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); - // } + /* 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), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1 ether, + CurveBuilder.singlePointCurve(1, 0 ether), + swapper + ), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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), + baseOutputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1 ether, + CurveBuilder.singlePointCurve(1, 0 ether), + swapper + ), + cosignerData: cosignerData, + cosignature: bytes("") + }); + 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 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + outputAmount, + CurveBuilder.singlePointCurve(1000, int256(inputAmount * 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 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + outputAmount, + 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 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + outputAmount, + 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 + 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 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 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 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 2000, + 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 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 2000, + 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 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 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 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 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 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1000, + 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), + 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), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1000, + 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 1659030395, output will still be 1000. One block later at 1659030396, + // the first decay will occur and the output will be 999. + function testV3ResolveFirstDecay() public { + uint256 startBlock = 1659029740; + uint256 currentBlock = 1659030395; // 1659030395 - 1659029740 = 655 = 0.00999 * 65535 + uint16 relativeEndBlock = 65535; + vm.roll(currentBlock); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentBlock: currentBlock, + startBlock: startBlock, + deadline: 1659130540, + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + 1000, + 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 > 0); + 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 + ), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + uint256(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); + } + + /* 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("") + }); + 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))); diff --git a/test/util/OutputsBuilder.sol b/test/util/OutputsBuilder.sol index f0796be5..9943d273 100644 --- a/test/util/OutputsBuilder.sol +++ b/test/util/OutputsBuilder.sol @@ -92,35 +92,25 @@ library OutputsBuilder { return result; } - function singleV3Dutch(address token, uint256 amount, uint256 endAmount, address recipient) + function singleV3Dutch(address token, uint256 amount, NonlinearDutchDecay memory curve, address recipient) internal pure returns (V3DutchOutput[] memory) { - int256 delta = int256(amount - endAmount); - NonlinearDutchDecay memory curve = NonlinearDutchDecay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(0, 0)), - relativeAmounts: ArrayBuilder.fillInt(1, delta) - }); V3DutchOutput[] memory result = new V3DutchOutput[](1); result[0] = V3DutchOutput(token, amount, curve, recipient); return result; } - function multipleV3Dutch(address token, uint256[] memory amounts, uint256 endAmount, address recipient) + 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++) { - int256 delta = int256(amounts[i] - endAmount); - NonlinearDutchDecay memory curve = NonlinearDutchDecay({ - relativeBlocks: toUint256(ArrayBuilder.fillUint16(0, 0)), - relativeAmounts: ArrayBuilder.fillInt(1, delta) - }); - result[i] = V3DutchOutput(token, amounts[i], curve, recipient); + result[i] = V3DutchOutput(token, amounts[i], curves[i], recipient); } return result; } -} +} \ No newline at end of file From 727a298bf89e57f5460f4e786fb5f109d0d19ef9 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Tue, 3 Sep 2024 17:03:41 -0400 Subject: [PATCH 17/24] Forge fmt --- test/lib/Uint16Array.t.sol | 8 +- test/reactors/V3DutchOrderReactor.t.sol | 167 ++++++------------------ test/util/OutputsBuilder.sol | 13 +- 3 files changed, 51 insertions(+), 137 deletions(-) diff --git a/test/lib/Uint16Array.t.sol b/test/lib/Uint16Array.t.sol index 2d0ede85..e87ae21f 100644 --- a/test/lib/Uint16Array.t.sol +++ b/test/lib/Uint16Array.t.sol @@ -5,7 +5,13 @@ 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"; +import { + Uint16ArrayLibrary, + Uint16Array, + toUint16Array, + InvalidArrLength, + IndexOutOfBounds +} from "../../src/types/Uint16Array.sol"; contract Uint16ArrayTest is Test { using Uint16ArrayLibrary for Uint16Array; diff --git a/test/reactors/V3DutchOrderReactor.t.sol b/test/reactors/V3DutchOrderReactor.t.sol index 77d2c2e3..372cae51 100644 --- a/test/reactors/V3DutchOrderReactor.t.sol +++ b/test/reactors/V3DutchOrderReactor.t.sol @@ -33,8 +33,10 @@ import {sub} from "../../src/lib/MathExt.sol"; contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { using OrderInfoBuilder for OrderInfo; - using V3DutchOrderLib for V3DutchOrder; + using V3DutchOrderLib for V3DutchOrder; + OrderQuoter quoter; + using {sub} for uint256; constructor() { @@ -114,10 +116,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, 0.8 ether, CurveBuilder.emptyCurve(), 1 ether), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1 ether, - CurveBuilder.singlePointCurve(1, 0 ether), - swapper + address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper ), cosignerData: cosignerData, cosignature: bytes("") @@ -144,10 +143,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1 ether, - CurveBuilder.singlePointCurve(1, .2 ether), - swapper + address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.2 ether), swapper ), cosignerData: cosignerData, cosignature: bytes("") @@ -174,10 +170,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1 ether, - CurveBuilder.singlePointCurve(1, .2 ether), - swapper + address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.2 ether), swapper ), cosignerData: cosignerData, cosignature: bytes("") @@ -208,10 +201,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, 0.8 ether, CurveBuilder.emptyCurve(), 1 ether), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - outputAmount, - CurveBuilder.singlePointCurve(1, 0 ether), - swapper + address(tokenOut), outputAmount, CurveBuilder.singlePointCurve(1, 0 ether), swapper ), cosignerData: cosignerData, cosignature: bytes("") @@ -248,10 +238,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1 ether, - CurveBuilder.singlePointCurve(1, .1 ether), - swapper + address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper ), cosignerData: cosignerData, cosignature: bytes("") @@ -287,10 +274,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1 ether, - CurveBuilder.singlePointCurve(1, .1 ether), - swapper + address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper ), cosignerData: cosignerData, cosignature: bytes("") @@ -321,10 +305,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1 ether, - CurveBuilder.singlePointCurve(1, .1 ether), - swapper + address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper ), cosignerData: cosignerData, cosignature: bytes("") @@ -363,10 +344,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1 ether, - CurveBuilder.singlePointCurve(1, .1 ether), - swapper + address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper ), cosignerData: cosignerData, cosignature: bytes("") @@ -400,10 +378,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1 ether, - CurveBuilder.singlePointCurve(1, .1 ether), - swapper + address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper ), cosignerData: cosignerData, cosignature: bytes("") @@ -435,10 +410,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { cosigner: wrongCosigner, baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1 ether, - CurveBuilder.singlePointCurve(1, 0 ether), - swapper + address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper ), cosignerData: cosignerData, cosignature: bytes("") @@ -465,10 +437,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { cosigner: wrongCosigner, baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1 ether, - CurveBuilder.singlePointCurve(1, 0 ether), - swapper + address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper ), cosignerData: cosignerData, cosignature: bytes("") @@ -495,7 +464,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { input: V3DutchInput( tokenIn, inputAmount, - CurveBuilder.singlePointCurve(1000, 0-int256(inputAmount * 10 / 100)), + CurveBuilder.singlePointCurve(1000, 0 - int256(inputAmount * 10 / 100)), inputAmount * 110 / 100 ), outputs: OutputsBuilder.singleV3Dutch( @@ -528,7 +497,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { input: V3DutchInput( tokenIn, inputAmount, - CurveBuilder.singlePointCurve(1000, 0-int256(inputAmount * 10 / 100)), + CurveBuilder.singlePointCurve(1000, 0 - int256(inputAmount * 10 / 100)), inputAmount * 110 / 100 ), outputs: OutputsBuilder.singleV3Dutch( @@ -566,7 +535,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { input: V3DutchInput( tokenIn, inputAmount, - CurveBuilder.singlePointCurve(1000, 0-int256(inputAmount * 10 / 100)), + CurveBuilder.singlePointCurve(1000, 0 - int256(inputAmount * 10 / 100)), inputAmount * 110 / 100 ), outputs: OutputsBuilder.singleV3Dutch( @@ -598,17 +567,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: currentBlock + 100, deadline: currentBlock + 200, - input: V3DutchInput( - tokenIn, - 1000, - CurveBuilder.singlePointCurve(200, 1000), - 2000 - ), + input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(200, 1000), 2000), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 0, - CurveBuilder.singlePointCurve(200, -100), - address(0) + address(tokenOut), 0, CurveBuilder.singlePointCurve(200, -100), address(0) ) }) ); @@ -626,17 +587,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: currentBlock - 100, deadline: currentBlock + 200, - input: V3DutchInput( - tokenIn, - 0, - CurveBuilder.singlePointCurve(100, 0), - 0 - ), + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(100, 0), 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 2000, - CurveBuilder.singlePointCurve(200, 1000), - address(0) + address(tokenOut), 2000, CurveBuilder.singlePointCurve(200, 1000), address(0) ) }) ); @@ -654,17 +607,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: currentBlock - 200, deadline: currentBlock + 200, - input: V3DutchInput( - tokenIn, - 100, - CurveBuilder.singlePointCurve(200, 100), - 100 - ), + input: V3DutchInput(tokenIn, 100, CurveBuilder.singlePointCurve(200, 100), 100), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 2000, - CurveBuilder.singlePointCurve(200, 1000), - address(0) + address(tokenOut), 2000, CurveBuilder.singlePointCurve(200, 1000), address(0) ) }) ); @@ -682,17 +627,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: currentBlock - 100, deadline: currentBlock + 200, - input: V3DutchInput( - tokenIn, - 1000, - CurveBuilder.singlePointCurve(200, 1000), - 1000 - ), + input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(200, 1000), 1000), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1000, - CurveBuilder.singlePointCurve(200, 0), - address(0) + address(tokenOut), 1000, CurveBuilder.singlePointCurve(200, 0), address(0) ) }) ); @@ -710,17 +647,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: currentBlock - 100, deadline: currentBlock + 200, - input: V3DutchInput( - tokenIn, - 1000, - CurveBuilder.singlePointCurve(100, 1000), - 1000 - ), + input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(100, 1000), 1000), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1000, - CurveBuilder.singlePointCurve(100, 0), - address(0) + address(tokenOut), 1000, CurveBuilder.singlePointCurve(100, 0), address(0) ) }) ); @@ -740,17 +669,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: 1659029740, deadline: 1659130540, - input: V3DutchInput( - tokenIn, - 0, - CurveBuilder.singlePointCurve(relativeEndBlock, 0), - 0 - ), + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1000, - CurveBuilder.singlePointCurve(relativeEndBlock, 100), - address(0) + address(tokenOut), 1000, CurveBuilder.singlePointCurve(relativeEndBlock, 100), address(0) ) }) ); @@ -777,10 +698,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { deadline: 1659130540, input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0), outputs: OutputsBuilder.multipleV3Dutch( - address(tokenOut), - Solarray.uint256s(1000, 10000, 2000), - curves, - address(0) + address(tokenOut), Solarray.uint256s(1000, 10000, 2000), curves, address(0) ) }) ); @@ -805,10 +723,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { deadline: 1659130540, input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1000, - CurveBuilder.singlePointCurve(relativeEndBlock, 100), - address(0) + address(tokenOut), 1000, CurveBuilder.singlePointCurve(relativeEndBlock, 100), address(0) ) }) ); @@ -833,10 +748,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { deadline: 1659130540, input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - 1000, - CurveBuilder.singlePointCurve(relativeEndBlock, 100), - address(0) + address(tokenOut), 1000, CurveBuilder.singlePointCurve(relativeEndBlock, 100), address(0) ) }) ); @@ -864,17 +776,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: uint256(currentBlock), startBlock: uint256(decayStartBlock), deadline: type(uint256).max, - input: V3DutchInput( - tokenIn, - 0, - CurveBuilder.singlePointCurve(decayDuration, 0), - 0 - ), + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(decayDuration, 0), 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), - uint256(startAmount), - CurveBuilder.singlePointCurve(decayDuration, 0 - int256(decayAmount)), - address(0) + address(tokenOut), + uint256(startAmount), + CurveBuilder.singlePointCurve(decayDuration, 0 - int256(decayAmount)), + address(0) ) }) ); diff --git a/test/util/OutputsBuilder.sol b/test/util/OutputsBuilder.sol index 9943d273..9c6e4943 100644 --- a/test/util/OutputsBuilder.sol +++ b/test/util/OutputsBuilder.sol @@ -102,15 +102,16 @@ library OutputsBuilder { return result; } - function multipleV3Dutch(address token, uint256[] memory amounts, NonlinearDutchDecay[] memory curves, address recipient) - internal - pure - returns (V3DutchOutput[] memory) - { + 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); } return result; } -} \ No newline at end of file +} From 8564c9b961d8816a0c2c0ba8507a25ef75f56e6f Mon Sep 17 00:00:00 2001 From: Cody Born Date: Tue, 3 Sep 2024 17:49:15 -0400 Subject: [PATCH 18/24] More tests --- test/lib/NonLinearDutchDecayLib.t.sol | 1 - test/reactors/V3DutchOrderReactor.t.sol | 151 +++++++++++++++++++++++- 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index ed9d536d..5389a77a 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -220,7 +220,6 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { // can't have neg prices vm.assume(startAmount >= decayAmount); vm.assume(startAmount <= UINT256_MAX - decayAmount); - vm.assume(decayDuration > 0); NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(decayDuration, int256(decayAmount)); snapStart("V3-DutchDecayNegative"); diff --git a/test/reactors/V3DutchOrderReactor.t.sol b/test/reactors/V3DutchOrderReactor.t.sol index 372cae51..fc39a1a3 100644 --- a/test/reactors/V3DutchOrderReactor.t.sol +++ b/test/reactors/V3DutchOrderReactor.t.sol @@ -767,7 +767,6 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { uint16 decayDuration, uint256 decayAmount ) public { - vm.assume(decayAmount > 0); vm.assume(decayAmount < 2 ** 255 - 1); vm.assume(startAmount <= UINT256_MAX - decayAmount); @@ -791,6 +790,156 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { 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), + outputs: OutputsBuilder.singleV3Dutch( + address(tokenOut), + uint256(startAmount), + 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), + outputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 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), + outputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1000, 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); + } + /* Test helpers */ struct TestDutchOrderSpec { From c66e04636844461f4867560261c7d77060025d91 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Wed, 4 Sep 2024 17:30:52 -0400 Subject: [PATCH 19/24] Fix old exclusivity test --- test/reactors/ExclusiveDutchOrderReactor.t.sol | 1 + 1 file changed, 1 insertion(+) 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); From 42cf3ca3244c6863e9bee3e932877f8d47d1a669 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Wed, 4 Sep 2024 17:59:32 -0400 Subject: [PATCH 20/24] Consolidate decay logic --- .forge-snapshots/EthOutputTestEthOutput.snap | 2 +- .../SwapRouter02ExecutorExecute.snap | 2 +- ...outer02ExecutorExecuteAlreadyApproved.snap | 2 +- .forge-snapshots/V3-DutchDecay.snap | 2 +- .../V3-DutchDecayFullyDecayed.snap | 2 +- .../V3-DutchDecayFullyDecayedNegative.snap | 2 +- .../V3-ExtendedMultiPointDutchDecay.snap | 2 +- .forge-snapshots/V3-MultiPointDutchDecay.snap | 2 +- src/lib/DutchDecayLib.sol | 35 +++++++++++++----- src/lib/NonlinearDutchDecayLib.sol | 36 +++---------------- 10 files changed, 40 insertions(+), 47 deletions(-) diff --git a/.forge-snapshots/EthOutputTestEthOutput.snap b/.forge-snapshots/EthOutputTestEthOutput.snap index 454b0839..21822adb 100644 --- a/.forge-snapshots/EthOutputTestEthOutput.snap +++ b/.forge-snapshots/EthOutputTestEthOutput.snap @@ -1 +1 @@ -155866 \ No newline at end of file +155949 \ No newline at end of file diff --git a/.forge-snapshots/SwapRouter02ExecutorExecute.snap b/.forge-snapshots/SwapRouter02ExecutorExecute.snap index c31b0cf2..5d18b657 100644 --- a/.forge-snapshots/SwapRouter02ExecutorExecute.snap +++ b/.forge-snapshots/SwapRouter02ExecutorExecute.snap @@ -1 +1 @@ -262691 \ No newline at end of file +262774 \ No newline at end of file diff --git a/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap b/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap index cb0f5e97..11353630 100644 --- a/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap +++ b/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap @@ -1 +1 @@ -117810 \ No newline at end of file +117893 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecay.snap b/.forge-snapshots/V3-DutchDecay.snap index 9227f3bd..8d4a50bd 100644 --- a/.forge-snapshots/V3-DutchDecay.snap +++ b/.forge-snapshots/V3-DutchDecay.snap @@ -1 +1 @@ -17352 \ No newline at end of file +17292 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap index 55ffc0c6..4477fe0f 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap @@ -1 +1 @@ -14810 \ No newline at end of file +14846 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap index 718fee6a..708b23e1 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap @@ -1 +1 @@ -14662 \ No newline at end of file +14698 \ No newline at end of file diff --git a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap index fa08fe03..e90f3e45 100644 --- a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap @@ -1 +1 @@ -208112 \ No newline at end of file +208379 \ No newline at end of file diff --git a/.forge-snapshots/V3-MultiPointDutchDecay.snap b/.forge-snapshots/V3-MultiPointDutchDecay.snap index a507085d..50e25964 100644 --- a/.forge-snapshots/V3-MultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-MultiPointDutchDecay.snap @@ -1 +1 @@ -51174 \ No newline at end of file +51240 \ No newline at end of file diff --git a/src/lib/DutchDecayLib.sol b/src/lib/DutchDecayLib.sol index f6b4d551..c5c4eef4 100644 --- a/src/lib/DutchDecayLib.sol +++ b/src/lib/DutchDecayLib.sol @@ -37,14 +37,33 @@ 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) { + if (currentPoint >= endPoint) { + return endAmount; + } + unchecked { + uint256 elapsed = currentPoint - startPoint; + uint256 duration = endPoint - startPoint; + if (endAmount < startAmount) { + return startAmount - (startAmount - endAmount).mulDivDown(elapsed, duration); + } else { + return startAmount + (endAmount - startAmount).mulDivUp(elapsed, duration); } } } diff --git a/src/lib/NonlinearDutchDecayLib.sol b/src/lib/NonlinearDutchDecayLib.sol index 9784081e..df833695 100644 --- a/src/lib/NonlinearDutchDecayLib.sol +++ b/src/lib/NonlinearDutchDecayLib.sol @@ -6,6 +6,7 @@ import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../lib/V3DutchOr import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; import {sub} from "./MathExt.sol"; import {Uint16ArrayLibrary, Uint16Array, fromUnderlying} from "../types/Uint16Array.sol"; +import {DutchDecayLib} from "./DutchDecayLib.sol"; /// @notice thrown when the decay curve is invalid error InvalidDecayCurve(); @@ -38,7 +39,7 @@ library NonlinearDutchDecayLib { uint16 blockDelta = uint16(block.number - decayStartBlock); // Special case for when we need to use the decayStartBlock (0) if (relativeBlocks.getElement(0) > blockDelta) { - return linearDecay( + return DutchDecayLib.linearDecay( 0, relativeBlocks.getElement(0), blockDelta, startAmount, startAmount.sub(curve.relativeAmounts[0]) ); } @@ -46,7 +47,7 @@ library NonlinearDutchDecayLib { (uint16 prev, uint16 next) = locateCurvePosition(curve, blockDelta); uint256 lastAmount = startAmount.sub(curve.relativeAmounts[prev]); uint256 nextAmount = startAmount.sub(curve.relativeAmounts[next]); - return linearDecay( + return DutchDecayLib.linearDecay( relativeBlocks.getElement(prev), relativeBlocks.getElement(next), blockDelta, lastAmount, nextAmount ); } @@ -72,33 +73,6 @@ library NonlinearDutchDecayLib { return (next - 1, next - 1); } - /// @notice returns the linear interpolation between the two points - /// @param startBlock The start of the decay - /// @param endBlock The end of the decay - /// @param currentBlock 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( - uint16 startBlock, - uint16 endBlock, - uint16 currentBlock, - uint256 startAmount, - uint256 endAmount - ) internal pure returns (uint256) { - if (currentBlock >= endBlock) { - return endAmount; - } - unchecked { - uint256 elapsed = currentBlock - startBlock; - uint256 duration = endBlock - startBlock; - if (endAmount < startAmount) { - return startAmount - (startAmount - endAmount).mulDivDown(elapsed, duration); - } else { - return startAmount + (endAmount - startAmount).mulDivUp(elapsed, duration); - } - } - } - /// @notice returns a decayed output using the given dutch spec and times /// @param output The output to decay /// @param decayStartBlock The block to start decaying @@ -108,7 +82,7 @@ library NonlinearDutchDecayLib { view returns (OutputToken memory result) { - uint256 decayedOutput = NonlinearDutchDecayLib.decay(output.curve, output.startAmount, decayStartBlock); + uint256 decayedOutput = decay(output.curve, output.startAmount, decayStartBlock); result = OutputToken(output.token, decayedOutput, output.recipient); } @@ -139,7 +113,7 @@ library NonlinearDutchDecayLib { view returns (InputToken memory result) { - uint256 decayedInput = NonlinearDutchDecayLib.decay(input.curve, input.startAmount, decayStartBlock); + uint256 decayedInput = decay(input.curve, input.startAmount, decayStartBlock); result = InputToken(input.token, decayedInput, input.maxAmount); } } From f71e5cceb904376ccbf43b5d5c57815d9b9358b0 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Thu, 12 Sep 2024 18:14:04 -0400 Subject: [PATCH 21/24] Address PR feedback --- ...Base-V3DutchOrder-BaseExecuteSingleWithFee.snap | 2 +- .../Base-V3DutchOrder-ExecuteBatch.snap | 2 +- ...e-V3DutchOrder-ExecuteBatchMultipleOutputs.snap | 2 +- ...ExecuteBatchMultipleOutputsDifferentTokens.snap | 2 +- ...Base-V3DutchOrder-ExecuteBatchNativeOutput.snap | 2 +- .../Base-V3DutchOrder-ExecuteSingle.snap | 2 +- ...ase-V3DutchOrder-ExecuteSingleNativeOutput.snap | 2 +- .../Base-V3DutchOrder-ExecuteSingleValidation.snap | 2 +- .../Base-V3DutchOrder-RevertInvalidNonce.snap | 2 +- .../Base-V3DutchOrder-V3-ExclusiveFiller.snap | 2 +- .../Base-V3DutchOrder-V3-InputOverride.snap | 2 +- .../Base-V3DutchOrder-V3-OutputOverride.snap | 2 +- src/lib/ExclusivityLib.sol | 9 ++++++--- src/lib/NonlinearDutchDecayLib.sol | 6 ++---- src/lib/V3DutchOrderLib.sol | 7 +++++++ src/reactors/V3DutchOrderReactor.sol | 14 +++++--------- 16 files changed, 32 insertions(+), 28 deletions(-) diff --git a/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap index f096fc15..fc99fc2c 100644 --- a/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -192980 \ No newline at end of file +193030 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap index 6f25147c..281502cb 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -219432 \ No newline at end of file +219532 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap index 173e14c0..3ee82d33 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -231439 \ No newline at end of file +231539 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index 921318a6..bdfbf883 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -287363 \ No newline at end of file +287463 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap index b67cac65..a08c8aca 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -212951 \ No newline at end of file +213051 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap index 8ae6c44e..343469df 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -159368 \ No newline at end of file +159418 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap index 9507876d..82b5572c 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -144926 \ No newline at end of file +144976 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap index 6523b747..a02e6433 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -168674 \ No newline at end of file +168724 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap b/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap index 4159e5c8..47f46b35 100644 --- a/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap +++ b/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap @@ -1 +1 @@ -37962 \ No newline at end of file +38012 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap b/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap index 32fb1f75..d07684a5 100644 --- a/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap +++ b/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap @@ -1 +1 @@ -163306 \ No newline at end of file +163356 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap b/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap index fde53516..7e10c96f 100644 --- a/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap +++ b/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap @@ -1 +1 @@ -163383 \ No newline at end of file +163433 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap b/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap index c7d15444..7e1209e1 100644 --- a/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap +++ b/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap @@ -1 +1 @@ -163332 \ No newline at end of file +163382 \ No newline at end of file diff --git a/src/lib/ExclusivityLib.sol b/src/lib/ExclusivityLib.sol index 00613777..81eb6375 100644 --- a/src/lib/ExclusivityLib.sol +++ b/src/lib/ExclusivityLib.sol @@ -20,7 +20,7 @@ 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 exclusivityEnd The exclusivity end time/block + /// @param exclusivityEnd The exclusivity end time /// @param exclusivityOverrideBps The exclusivity override BPS function handleExclusiveOverrideTimestamp( ResolvedOrder memory order, @@ -34,7 +34,7 @@ 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 exclusivityEnd The exclusivity end time/block + /// @param exclusivityEnd The exclusivity end block number /// @param exclusivityOverrideBps The exclusivity override BPS function handleExclusiveOverrideBlock( ResolvedOrder memory order, @@ -48,7 +48,7 @@ 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 exclusivityEnd The exclusivity end time/block + /// @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( @@ -81,6 +81,9 @@ 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 diff --git a/src/lib/NonlinearDutchDecayLib.sol b/src/lib/NonlinearDutchDecayLib.sol index df833695..eb657e47 100644 --- a/src/lib/NonlinearDutchDecayLib.sol +++ b/src/lib/NonlinearDutchDecayLib.sol @@ -97,10 +97,8 @@ library NonlinearDutchDecayLib { { uint256 outputLength = outputs.length; result = new OutputToken[](outputLength); - unchecked { - for (uint256 i = 0; i < outputLength; i++) { - result[i] = decay(outputs[i], decayStartBlock); - } + for (uint256 i = 0; i < outputLength; i++) { + result[i] = decay(outputs[i], decayStartBlock); } } diff --git a/src/lib/V3DutchOrderLib.sol b/src/lib/V3DutchOrderLib.sol index 60a6145d..4286f517 100644 --- a/src/lib/V3DutchOrderLib.sol +++ b/src/lib/V3DutchOrderLib.sol @@ -165,4 +165,11 @@ library V3DutchOrderLib { ) ); } + + /// @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/V3DutchOrderReactor.sol b/src/reactors/V3DutchOrderReactor.sol index 465acec1..d4b98a55 100644 --- a/src/reactors/V3DutchOrderReactor.sol +++ b/src/reactors/V3DutchOrderReactor.sol @@ -8,9 +8,10 @@ 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 {CosignerLib} from "../lib/CosignerLib.sol"; -/// @notice Reactor for non-linear dutch orders -/// @dev Non-linear orders must be cosigned by the specified cosigner to override starting block and value +/// @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 @@ -111,12 +112,7 @@ contract V3DutchOrderReactor is BaseReactor { if (order.info.deadline < block.timestamp) { revert DeadlineReached(); } - (bytes32 r, bytes32 s) = abi.decode(order.cosignature, (bytes32, bytes32)); - uint8 v = uint8(order.cosignature[64]); - // cosigner signs over (orderHash || cosignerData) - address signer = ecrecover(keccak256(abi.encodePacked(orderHash, abi.encode(order.cosignerData))), v, r, s); - if (order.cosigner != signer || signer == address(0)) { - revert InvalidCosignature(); - } + + CosignerLib.verify(order.cosigner, order.cosignerDigest(orderHash), order.cosignature); } } From 954ac6a93719300dc24c3f0d9a1522ff8d180a9b Mon Sep 17 00:00:00 2001 From: Cody Born Date: Fri, 13 Sep 2024 14:42:00 -0400 Subject: [PATCH 22/24] PR feedback --- src/reactors/V3DutchOrderReactor.sol | 2 +- test/lib/NonLinearDutchDecayLib.t.sol | 1 - test/reactors/V2DutchOrderReactor.t.sol | 2 +- test/reactors/V3DutchOrderReactor.t.sol | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/reactors/V3DutchOrderReactor.sol b/src/reactors/V3DutchOrderReactor.sol index d4b98a55..646714e5 100644 --- a/src/reactors/V3DutchOrderReactor.sol +++ b/src/reactors/V3DutchOrderReactor.sol @@ -112,7 +112,7 @@ contract V3DutchOrderReactor is BaseReactor { if (order.info.deadline < block.timestamp) { revert DeadlineReached(); } - + CosignerLib.verify(order.cosigner, order.cosignerDigest(orderHash), order.cosignature); } } diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index 5389a77a..90d073f3 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -422,7 +422,6 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { uint256 startAmount = 1 ether; NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(ArrayBuilder.fillUint16(16, 1), ArrayBuilder.fillInt(17, 0)); - vm.roll(150); vm.expectRevert(InvalidDecayCurve.selector); mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); } 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 index fc39a1a3..32973d35 100644 --- a/test/reactors/V3DutchOrderReactor.t.sol +++ b/test/reactors/V3DutchOrderReactor.t.sol @@ -324,7 +324,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); } - function testV3ExclusiveOverrideInvalidCallerCosignedAmountOutput() public { + function testV3AppliesExclusiveOverride() public { uint256 inputAmount = 1 ether; uint256 outputAmount = 1 ether; uint256 exclusivityOverrideBps = 10; From e29b2876bd3c44017854abf6e6b3d8199e13cfb7 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Wed, 18 Sep 2024 18:49:02 -0400 Subject: [PATCH 23/24] Gas Adjustment Feature (#273) * Gas adjustment feature and tests * Update 712 structs * Final tests * Address PR feedback * Natspec * PR Feedback * edge case fix and gas improvements --- ...V3DutchOrder-BaseExecuteSingleWithFee.snap | 2 +- .../Base-V3DutchOrder-ExecuteBatch.snap | 2 +- ...utchOrder-ExecuteBatchMultipleOutputs.snap | 2 +- ...teBatchMultipleOutputsDifferentTokens.snap | 2 +- ...V3DutchOrder-ExecuteBatchNativeOutput.snap | 2 +- .../Base-V3DutchOrder-ExecuteSingle.snap | 2 +- ...3DutchOrder-ExecuteSingleNativeOutput.snap | 2 +- ...-V3DutchOrder-ExecuteSingleValidation.snap | 2 +- .../Base-V3DutchOrder-RevertInvalidNonce.snap | 2 +- .../Base-V3DutchOrder-V3-ExclusiveFiller.snap | 2 +- .../Base-V3DutchOrder-V3-InputOverride.snap | 2 +- .../Base-V3DutchOrder-V3-OutputOverride.snap | 2 +- .forge-snapshots/EthOutputTestEthOutput.snap | 2 +- .../SwapRouter02ExecutorExecute.snap | 2 +- ...outer02ExecutorExecuteAlreadyApproved.snap | 2 +- .forge-snapshots/V3-DutchDecay.snap | 2 +- .forge-snapshots/V3-DutchDecayBounded.snap | 2 +- .../V3-DutchDecayFullyDecayed.snap | 2 +- .../V3-DutchDecayFullyDecayedNegative.snap | 2 +- .forge-snapshots/V3-DutchDecayNegative.snap | 2 +- .forge-snapshots/V3-DutchDecayNoDecay.snap | 2 +- .forge-snapshots/V3-DutchDecayNoDecayYet.snap | 2 +- .../V3-DutchDecayNoDecayYetNegative.snap | 2 +- .forge-snapshots/V3-DutchDecayRange.snap | 1 + .../V3-ExtendedMultiPointDutchDecay.snap | 2 +- .forge-snapshots/V3-MultiPointDutchDecay.snap | 2 +- src/lib/DutchDecayLib.sol | 32 +- src/lib/MathExt.sol | 86 ++- src/lib/NonlinearDutchDecayLib.sol | 57 +- src/lib/V3DutchOrderLib.sol | 64 +- src/reactors/V3DutchOrderReactor.sol | 26 +- test/lib/DutchDecayLib.t.sol | 38 +- test/lib/MathExt.t.sol | 93 ++- test/lib/NonLinearDutchDecayLib.t.sol | 162 +++-- test/reactors/V3DutchOrderReactor.t.sol | 607 +++++++++++++++--- test/util/OutputsBuilder.sol | 20 +- 36 files changed, 989 insertions(+), 247 deletions(-) create mode 100644 .forge-snapshots/V3-DutchDecayRange.snap diff --git a/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap index fc99fc2c..a4f7c27f 100644 --- a/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-V3DutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -193030 \ No newline at end of file +198132 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap index 281502cb..5a9bc93c 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -219532 \ No newline at end of file +229797 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap index 3ee82d33..689ecd8a 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -231539 \ No newline at end of file +243298 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index bdfbf883..1deec62d 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -287463 \ No newline at end of file +300708 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap index a08c8aca..00eab807 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -213051 \ No newline at end of file +223323 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap index 343469df..5f3411e0 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -159418 \ No newline at end of file +164517 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap index 82b5572c..76a79751 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -144976 \ No newline at end of file +150079 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap index a02e6433..3b1435ad 100644 --- a/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-V3DutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -168724 \ No newline at end of file +173829 \ No newline at end of file diff --git a/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap b/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap index 47f46b35..f497df92 100644 --- a/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap +++ b/.forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap @@ -1 +1 @@ -38012 \ No newline at end of file +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 index d07684a5..0383dc25 100644 --- a/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap +++ b/.forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap @@ -1 +1 @@ -163356 \ No newline at end of file +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 index 7e10c96f..824d4c13 100644 --- a/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap +++ b/.forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap @@ -1 +1 @@ -163433 \ No newline at end of file +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 index 7e1209e1..c11c0243 100644 --- a/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap +++ b/.forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap @@ -1 +1 @@ -163382 \ No newline at end of file +168467 \ No newline at end of file diff --git a/.forge-snapshots/EthOutputTestEthOutput.snap b/.forge-snapshots/EthOutputTestEthOutput.snap index 21822adb..b1321526 100644 --- a/.forge-snapshots/EthOutputTestEthOutput.snap +++ b/.forge-snapshots/EthOutputTestEthOutput.snap @@ -1 +1 @@ -155949 \ No newline at end of file +156451 \ No newline at end of file diff --git a/.forge-snapshots/SwapRouter02ExecutorExecute.snap b/.forge-snapshots/SwapRouter02ExecutorExecute.snap index 5d18b657..b48d97a4 100644 --- a/.forge-snapshots/SwapRouter02ExecutorExecute.snap +++ b/.forge-snapshots/SwapRouter02ExecutorExecute.snap @@ -1 +1 @@ -262774 \ 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 11353630..d8dbb860 100644 --- a/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap +++ b/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap @@ -1 +1 @@ -117893 \ 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 index 8d4a50bd..6fcf259d 100644 --- a/.forge-snapshots/V3-DutchDecay.snap +++ b/.forge-snapshots/V3-DutchDecay.snap @@ -1 +1 @@ -17292 \ No newline at end of file +20288 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayBounded.snap b/.forge-snapshots/V3-DutchDecayBounded.snap index 3af7d09a..1f168c59 100644 --- a/.forge-snapshots/V3-DutchDecayBounded.snap +++ b/.forge-snapshots/V3-DutchDecayBounded.snap @@ -1 +1 @@ -1070 \ No newline at end of file +1199 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap index 4477fe0f..02c7f1f5 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap @@ -1 +1 @@ -14846 \ No newline at end of file +14988 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap index 708b23e1..1c2819cd 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap @@ -1 +1 @@ -14698 \ No newline at end of file +14676 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNegative.snap b/.forge-snapshots/V3-DutchDecayNegative.snap index 3af7d09a..11b9a892 100644 --- a/.forge-snapshots/V3-DutchDecayNegative.snap +++ b/.forge-snapshots/V3-DutchDecayNegative.snap @@ -1 +1 @@ -1070 \ No newline at end of file +1271 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNoDecay.snap b/.forge-snapshots/V3-DutchDecayNoDecay.snap index d5fffd06..4b447b70 100644 --- a/.forge-snapshots/V3-DutchDecayNoDecay.snap +++ b/.forge-snapshots/V3-DutchDecayNoDecay.snap @@ -1 +1 @@ -5457 \ No newline at end of file +5899 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNoDecayYet.snap b/.forge-snapshots/V3-DutchDecayNoDecayYet.snap index ccc50fd7..9b08816d 100644 --- a/.forge-snapshots/V3-DutchDecayNoDecayYet.snap +++ b/.forge-snapshots/V3-DutchDecayNoDecayYet.snap @@ -1 +1 @@ -4281 \ No newline at end of file +4703 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap b/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap index ccc50fd7..9b08816d 100644 --- a/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap +++ b/.forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap @@ -1 +1 @@ -4281 \ No newline at end of file +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 index e90f3e45..3e62af5c 100644 --- a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap @@ -1 +1 @@ -208379 \ No newline at end of file +212154 \ No newline at end of file diff --git a/.forge-snapshots/V3-MultiPointDutchDecay.snap b/.forge-snapshots/V3-MultiPointDutchDecay.snap index 50e25964..18d7df22 100644 --- a/.forge-snapshots/V3-MultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-MultiPointDutchDecay.snap @@ -1 +1 @@ -51240 \ No newline at end of file +53460 \ No newline at end of file diff --git a/src/lib/DutchDecayLib.sol b/src/lib/DutchDecayLib.sol index c5c4eef4..755a97ea 100644 --- a/src/lib/DutchDecayLib.sol +++ b/src/lib/DutchDecayLib.sol @@ -54,18 +54,34 @@ library DutchDecayLib { 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; } - unchecked { - uint256 elapsed = currentPoint - startPoint; - uint256 duration = endPoint - startPoint; - if (endAmount < startAmount) { - return startAmount - (startAmount - endAmount).mulDivDown(elapsed, duration); - } else { - return startAmount + (endAmount - startAmount).mulDivUp(elapsed, duration); - } + 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/MathExt.sol b/src/lib/MathExt.sol index bb3615aa..e73c5164 100644 --- a/src/lib/MathExt.sol +++ b/src/lib/MathExt.sol @@ -1,17 +1,85 @@ pragma solidity ^0.8.0; +import {SafeCast} from "openzeppelin-contracts/utils/math/SafeCast.sol"; +import {Math} from "openzeppelin-contracts/utils/math/Math.sol"; + error NegativeUint(); -function sub(uint256 a, int256 b) pure returns (uint256) { - if (b < 0) { - // If b is negative, add its absolute value to a - return a + uint256(-b); - } else { - // If b is positive, subtract it from a - if (a < uint256(b)) { - revert NegativeUint(); +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. + /// @custom:throws NegativeUint if the subtraction would result in a negative value. + 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 { + // If b is positive, subtract it from a + if (a < uint256(b)) { + revert NegativeUint(); + } + + 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); } + } - return a - uint256(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 index eb657e47..95347095 100644 --- a/src/lib/NonlinearDutchDecayLib.sol +++ b/src/lib/NonlinearDutchDecayLib.sol @@ -4,28 +4,31 @@ 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 {sub} from "./MathExt.sol"; +import {MathExt} from "./MathExt.sol"; import {Uint16ArrayLibrary, Uint16Array, fromUnderlying} from "../types/Uint16Array.sol"; import {DutchDecayLib} from "./DutchDecayLib.sol"; -/// @notice thrown when the decay curve is invalid -error InvalidDecayCurve(); - /// @notice helpers for handling non-linear dutch order objects library NonlinearDutchDecayLib { using FixedPointMathLib for uint256; - using {sub} 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 - function decay(NonlinearDutchDecay memory curve, uint256 startAmount, uint256 decayStartBlock) - internal - view - returns (uint256 decayedAmount) - { + /// @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(); @@ -33,23 +36,28 @@ library NonlinearDutchDecayLib { // handle current block before decay or no decay if (decayStartBlock >= block.number || curve.relativeAmounts.length == 0) { - return startAmount; + return startAmount.bound(minAmount, maxAmount); } Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); uint16 blockDelta = uint16(block.number - decayStartBlock); + int256 curveDelta; // Special case for when we need to use the decayStartBlock (0) if (relativeBlocks.getElement(0) > blockDelta) { - return DutchDecayLib.linearDecay( - 0, relativeBlocks.getElement(0), blockDelta, startAmount, startAmount.sub(curve.relativeAmounts[0]) + curveDelta = + DutchDecayLib.linearDecay(0, relativeBlocks.getElement(0), blockDelta, 0, curve.relativeAmounts[0]); + } else { + // the current pos is within or after the curve + (uint16 prev, uint16 next) = locateCurvePosition(curve, blockDelta); + // get decay of only the relative amounts + curveDelta = DutchDecayLib.linearDecay( + relativeBlocks.getElement(prev), + relativeBlocks.getElement(next), + blockDelta, + curve.relativeAmounts[prev], + curve.relativeAmounts[next] ); } - // the current pos is within or after the curve - (uint16 prev, uint16 next) = locateCurvePosition(curve, blockDelta); - uint256 lastAmount = startAmount.sub(curve.relativeAmounts[prev]); - uint256 nextAmount = startAmount.sub(curve.relativeAmounts[next]); - return DutchDecayLib.linearDecay( - relativeBlocks.getElement(prev), relativeBlocks.getElement(next), blockDelta, lastAmount, nextAmount - ); + return startAmount.boundedSub(curveDelta, minAmount, maxAmount); } /// @notice Locates the current position on the curve using a binary search @@ -73,7 +81,7 @@ library NonlinearDutchDecayLib { return (next - 1, next - 1); } - /// @notice returns a decayed output using the given dutch spec and times + /// @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 @@ -82,11 +90,12 @@ library NonlinearDutchDecayLib { view returns (OutputToken memory result) { - uint256 decayedOutput = decay(output.curve, output.startAmount, decayStartBlock); + 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 times + /// @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 @@ -111,7 +120,7 @@ library NonlinearDutchDecayLib { view returns (InputToken memory result) { - uint256 decayedInput = decay(input.curve, input.startAmount, decayStartBlock); + 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 index 4286f517..b9f7ac48 100644 --- a/src/lib/V3DutchOrderLib.sol +++ b/src/lib/V3DutchOrderLib.sol @@ -24,6 +24,8 @@ struct V3DutchOrder { 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 @@ -53,6 +55,8 @@ struct V3DutchInput { 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 @@ -65,30 +69,49 @@ struct V3DutchOutput { 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 NON_LINEAR_DUTCH_ORDER_TYPE = abi.encodePacked( + bytes internal constant V3_DUTCH_ORDER_TYPE = abi.encodePacked( "V3DutchOrder(", "OrderInfo info,", "address cosigner,", + "uint256 startingBaseFee,", "V3DutchInput baseInput,", "V3DutchOutput[] baseOutputs)" ); - bytes internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE = abi.encodePacked( - "V3DutchOutput(", "address token,", "uint256 startAmount,", "NonlinearDutchDecay curve,", "address recipient)" + bytes internal constant V3_DUTCH_INPUT_TYPE = abi.encodePacked( + "V3DutchInput(", + "address token,", + "uint256 startAmount,", + "NonlinearDutchDecay curve,", + "uint256 maxAmount,", + "uint256 adjustmentPerGweiBaseFee)" ); - bytes32 internal constant NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH = keccak256(NON_LINEAR_DUTCH_OUTPUT_TYPE); + 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, NON_LINEAR_DUTCH_ORDER_TYPE, NON_LINEAR_DUTCH_OUTPUT_TYPE, OrderInfoLib.ORDER_INFO_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 @@ -96,10 +119,11 @@ library V3DutchOrderLib { abi.encodePacked( "V3DutchOrder witness)", NON_LINEAR_DECAY_TYPE, - NON_LINEAR_DUTCH_ORDER_TYPE, - NON_LINEAR_DUTCH_OUTPUT_TYPE, OrderInfoLib.ORDER_INFO_TYPE, - DutchOrderLib.TOKEN_PERMISSIONS_TYPE + DutchOrderLib.TOKEN_PERMISSIONS_TYPE, + V3_DUTCH_INPUT_TYPE, + V3_DUTCH_ORDER_TYPE, + V3_DUTCH_OUTPUT_TYPE ) ); @@ -117,7 +141,12 @@ library V3DutchOrderLib { function hash(V3DutchInput memory input) internal pure returns (bytes32) { return keccak256( abi.encode( - NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH, input.token, input.startAmount, hash(input.curve), input.maxAmount + V3_DUTCH_INPUT_TYPE_HASH, + input.token, + input.startAmount, + hash(input.curve), + input.maxAmount, + input.adjustmentPerGweiBaseFee ) ); } @@ -128,11 +157,13 @@ library V3DutchOrderLib { function hash(V3DutchOutput memory output) internal pure returns (bytes32) { return keccak256( abi.encode( - NON_LINEAR_DUTCH_OUTPUT_TYPE_HASH, + V3_DUTCH_OUTPUT_TYPE_HASH, output.token, output.startAmount, hash(output.curve), - output.recipient + output.recipient, + output.minAmount, + output.adjustmentPerGweiBaseFee ) ); } @@ -161,7 +192,12 @@ library V3DutchOrderLib { function hash(V3DutchOrder memory order) internal pure returns (bytes32) { return keccak256( abi.encode( - ORDER_TYPE_HASH, order.info.hash(), order.cosigner, hash(order.baseInput), hash(order.baseOutputs) + ORDER_TYPE_HASH, + order.info.hash(), + order.cosigner, + order.startingBaseFee, + hash(order.baseInput), + hash(order.baseOutputs) ) ); } diff --git a/src/reactors/V3DutchOrderReactor.sol b/src/reactors/V3DutchOrderReactor.sol index 646714e5..e9d88914 100644 --- a/src/reactors/V3DutchOrderReactor.sol +++ b/src/reactors/V3DutchOrderReactor.sol @@ -8,6 +8,9 @@ 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 @@ -25,6 +28,8 @@ contract V3DutchOrderReactor is BaseReactor { 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(); @@ -54,6 +59,7 @@ contract V3DutchOrderReactor is BaseReactor { _validateOrder(orderHash, order); _updateWithCosignerAmounts(order); + _updateWithGasAdjustment(order); resolvedOrder = ResolvedOrder({ info: order.info, @@ -92,7 +98,8 @@ contract V3DutchOrderReactor is BaseReactor { if (order.cosignerData.outputAmounts.length != order.baseOutputs.length) { revert InvalidCosignerOutput(); } - for (uint256 i = 0; i < order.baseOutputs.length; i++) { + 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) { @@ -104,6 +111,23 @@ contract V3DutchOrderReactor is BaseReactor { } } + 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 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/MathExt.t.sol b/test/lib/MathExt.t.sol index 73cf4463..2d8765b4 100644 --- a/test/lib/MathExt.t.sol +++ b/test/lib/MathExt.t.sol @@ -1,19 +1,21 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Test} from "forge-std/Test.sol"; +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 {sub, NegativeUint} from "../../src/lib/MathExt.sol"; +import {MathExt, NegativeUint} from "../../src/lib/MathExt.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; contract MathExtTest is Test { - using {sub} for uint256; + using MathExt for uint256; + + /* sub(uint256 a, int256 b) tests */ function testSubIntFromUint() public { - assertEq(uint256(2).sub(2), 0); - assertEq(uint256(2).sub(1), 1); - assertEq(uint256(2).sub(-1), 3); + 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 { @@ -31,11 +33,88 @@ contract MathExtTest is Test { function testSubIntFromUintNegativeUint() public { vm.expectRevert(NegativeUint.selector); - uint256(1).sub(2); + 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 index 90d073f3..da0f4440 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -4,7 +4,8 @@ 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 {NonlinearDutchDecayLib, InvalidDecayCurve} from "../../src/lib/NonlinearDutchDecayLib.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"; @@ -13,8 +14,14 @@ import {NegativeUint} from "../../src/lib/MathExt.sol"; /// @notice mock contract to test NonlinearDutchDecayLib functionality contract MockNonlinearDutchDecayLibContract { - function decay(NonlinearDutchDecay memory curve, uint256 startAmount, uint256 decayStartBlock) public view { - NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + function decay( + NonlinearDutchDecay memory curve, + uint256 startAmount, + uint256 decayStartBlock, + uint256 minAmount, + uint256 maxAmount + ) public view { + NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, minAmount, maxAmount); } } @@ -82,11 +89,19 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public { // Empty curve snapStart("V3-DutchDecayNoDecay"); - assertEq(NonlinearDutchDecayLib.decay(CurveBuilder.emptyCurve(), startAmount, decayStartBlock), startAmount); + 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 + NonlinearDutchDecayLib.decay( + CurveBuilder.singlePointCurve(1, 0), startAmount, decayStartBlock, startAmount, startAmount + ), + startAmount ); snapEnd(); } @@ -99,11 +114,11 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { snapStart("V3-DutchDecayNoDecayYet"); vm.roll(100); // at decayStartBlock - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, startAmount, 2 ether), startAmount); vm.roll(80); // before decayStartBlock - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, startAmount, 2 ether), startAmount); snapEnd(); } @@ -115,11 +130,11 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { snapStart("V3-DutchDecayNoDecayYetNegative"); vm.roll(100); // at decayStartBlock - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0, 1 ether), startAmount); vm.roll(80); // before decayStartBlock - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0, 1 ether), startAmount); snapEnd(); } @@ -130,16 +145,16 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); snapStart("V3-DutchDecay"); vm.roll(150); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.5 ether); vm.roll(180); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.8 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.8 ether); vm.roll(110); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.1 ether); vm.roll(190); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.9 ether); snapEnd(); } @@ -150,16 +165,16 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); snapStart("V3-DutchDecayNegative"); vm.roll(150); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.5 ether); vm.roll(180); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.2 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.2 ether); vm.roll(110); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.9 ether); vm.roll(190); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.1 ether); snapEnd(); } @@ -170,10 +185,10 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); snapStart("V3-DutchDecayFullyDecayed"); vm.roll(200); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 2 ether); vm.warp(250); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 2 ether); snapEnd(); } @@ -184,28 +199,44 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); snapStart("V3-DutchDecayFullyDecayedNegative"); vm.roll(200); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1 ether); vm.warp(250); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + 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, - uint256 decayAmount, + int256 decayAmount, uint256 decayStartBlock, - uint16 decayDuration + uint16 decayDuration, + uint256 minAmount, + uint256 maxAmount ) public { vm.assume(decayAmount > 0); - vm.assume(decayAmount < 2 ** 255 - 1); - vm.assume(startAmount <= UINT256_MAX - decayAmount); + 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); - assertGe(decayed, startAmount); - assertLe(decayed, startAmount + decayAmount); + uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, minAmount, maxAmount); + assertGe(decayed, minAmount); + assertLe(decayed, maxAmount); snapEnd(); } @@ -223,7 +254,7 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(decayDuration, int256(decayAmount)); snapStart("V3-DutchDecayNegative"); - uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock); + uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0, type(uint256).max); assertLe(decayed, startAmount); assertGe(decayed, startAmount - decayAmount); snapEnd(); @@ -243,31 +274,31 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); snapStart("V3-MultiPointDutchDecay"); vm.roll(50); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1 ether); vm.roll(150); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.5 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1.5 ether); vm.roll(200); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 2 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 2 ether); vm.roll(210); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.9 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1.9 ether); vm.roll(290); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.1 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1.1 ether); vm.roll(300); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 1 ether); vm.roll(350); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.5 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 0.5 ether); vm.roll(400); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 0 ether); vm.roll(500); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 0 ether); snapEnd(); } @@ -314,67 +345,67 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { snapStart("V3-ExtendedMultiPointDutchDecay"); vm.roll(50); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1 ether); vm.roll(150); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 1.05 ether); // halfway between 100 (1 ether) and 200 (1.1 ether) + 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.1 ether); // 1 + 0.1 ether + 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.15 ether); // halfway between 200 (1.1 ether) and 300 (1.2 ether) + 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.2 ether); // 1 + 0.2 ether + 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.25 ether); // halfway between 300 (1.2 ether) and 400 (1.3 ether) + 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.3 ether); // 1 + 0.3 ether + 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.35 ether); // halfway between 400 (1.3 ether) and 500 (1.4 ether) + 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.4 ether); // 1 + 0.4 ether + 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.5 ether); // 1 + 0.5 ether + 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.6 ether); // 1 + 0.6 ether + 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.7 ether); // 1 + 0.7 ether + 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.8 ether); // 1 + 0.8 ether + 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.9 ether); // 1 + 0.9 ether + 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), 2 ether); // 1 + 1 ether + 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.9 ether); // 1 + 0.9 ether + 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.8 ether); // 1 + 0.8 ether + 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.7 ether); // 1 + 0.7 ether + 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.6 ether); // 1 + 0.6 ether + 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.5 ether); // 1 + 0.5 ether + 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.45 ether); // 1 + 0.45 ether + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 1 ether, 2 ether), 1.45 ether); // 1 + 0.45 ether snapEnd(); } @@ -394,7 +425,7 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { decayAmounts[2] = 1 ether; // 0 ether NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts); vm.roll(350); - assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), 0.25 ether); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 2 ether), 0.25 ether); } function testDutchDecayToNegative() public { @@ -403,18 +434,17 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { int256 decayAmount = 2 ether; NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); vm.roll(150); - vm.expectRevert(NegativeUint.selector); - mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + assertEq(NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, 0 ether, 1 ether), 0); } function testDutchOverflowDecay() public { uint256 decayStartBlock = 100; uint256 startAmount = 1 ether; - int256 decayAmount = -(2 ** 255 - 1); + int256 decayAmount = type(int256).min; NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(100, decayAmount); vm.roll(150); vm.expectRevert(); - mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock, 0 ether, 1 ether); } function testDutchMismatchedDecay() public { @@ -422,7 +452,7 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { uint256 startAmount = 1 ether; NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(ArrayBuilder.fillUint16(16, 1), ArrayBuilder.fillInt(17, 0)); - vm.expectRevert(InvalidDecayCurve.selector); - mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock); + vm.expectRevert(NonlinearDutchDecayLib.InvalidDecayCurve.selector); + mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock, 1 ether, 1 ether); } } diff --git a/test/reactors/V3DutchOrderReactor.t.sol b/test/reactors/V3DutchOrderReactor.t.sol index 32973d35..55e63c22 100644 --- a/test/reactors/V3DutchOrderReactor.t.sol +++ b/test/reactors/V3DutchOrderReactor.t.sol @@ -29,7 +29,7 @@ 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 {sub} from "../../src/lib/MathExt.sol"; +import {MathExt} from "../../src/lib/MathExt.sol"; contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { using OrderInfoBuilder for OrderInfo; @@ -37,7 +37,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { OrderQuoter quoter; - using {sub} for uint256; + using MathExt for uint256; constructor() { quoter = new OrderQuoter(); @@ -67,7 +67,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { token: output.token, startAmount: output.amount, curve: CurveBuilder.emptyCurve(), - recipient: output.recipient + recipient: output.recipient, + minAmount: output.amount, + adjustmentPerGweiBaseFee: 0 }); } @@ -88,11 +90,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { info: request.info, cosigner: vm.addr(cosignerPrivateKey), baseInput: V3DutchInput( - request.input.token, request.input.amount, CurveBuilder.emptyCurve(), request.input.amount + request.input.token, request.input.amount, CurveBuilder.emptyCurve(), request.input.amount, 0 ), baseOutputs: outputs, cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); orderHash = order.hash(); order.cosignature = cosignOrder(orderHash, cosignerData); @@ -114,12 +117,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { 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), + baseInput: V3DutchInput(tokenIn, 0.8 ether, CurveBuilder.emptyCurve(), 1 ether, 0), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper + address(tokenOut), 1 ether, 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper ), cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); order.cosignature = cosignOrder(order.hash(), cosignerData); SignedOrder memory signedOrder = @@ -141,12 +145,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { V3DutchOrder memory order = V3DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), - baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether), + baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether, 0), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.2 ether), swapper + address(tokenOut), 1 ether, 0.8 ether, CurveBuilder.singlePointCurve(1, 0.2 ether), swapper ), cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); order.cosignature = cosignOrder(order.hash(), cosignerData); SignedOrder memory signedOrder = @@ -168,12 +173,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { V3DutchOrder memory order = V3DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), - baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether), + baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether, 0), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.2 ether), swapper + address(tokenOut), 1 ether, 0.8 ether, CurveBuilder.singlePointCurve(1, 0.2 ether), swapper ), cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); order.cosignature = cosignOrder(order.hash(), cosignerData); SignedOrder memory signedOrder = @@ -199,12 +205,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { 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), + baseInput: V3DutchInput(tokenIn, 0.8 ether, CurveBuilder.emptyCurve(), 1 ether, 0), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), outputAmount, CurveBuilder.singlePointCurve(1, 0 ether), swapper + address(tokenOut), outputAmount, outputAmount, CurveBuilder.singlePointCurve(1, 0 ether), swapper ), cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); order.cosignature = cosignOrder(order.hash(), cosignerData); SignedOrder memory signedOrder = @@ -236,12 +243,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { V3DutchOrder memory order = V3DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), - baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), + baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount, 0), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper + address(tokenOut), 1 ether, 0.9 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper ), cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); order.cosignature = cosignOrder(order.hash(), cosignerData); SignedOrder memory signedOrder = @@ -272,12 +280,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { V3DutchOrder memory order = V3DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), - baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), + baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount, 0), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper + address(tokenOut), 1 ether, 0.9 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper ), cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); order.cosignature = cosignOrder(order.hash(), cosignerData); SignedOrder memory signedOrder = @@ -303,12 +312,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { V3DutchOrder memory order = V3DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), - baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), + baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount, 0), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper + address(tokenOut), 1 ether, 0.9 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper ), cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); order.cosignature = cosignOrder(order.hash(), cosignerData); SignedOrder memory signedOrder = @@ -342,12 +352,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { V3DutchOrder memory order = V3DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), - baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), + baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount, 0), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper + address(tokenOut), 1 ether, 0.9 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper ), cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); order.cosignature = cosignOrder(order.hash(), cosignerData); SignedOrder memory signedOrder = @@ -376,12 +387,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { V3DutchOrder memory order = V3DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: vm.addr(cosignerPrivateKey), - baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount), + baseInput: V3DutchInput(tokenIn, inputAmount, CurveBuilder.emptyCurve(), inputAmount, 0), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper + address(tokenOut), 1 ether, 0.9 ether, CurveBuilder.singlePointCurve(1, 0.1 ether), swapper ), cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); order.cosignature = cosignOrder(order.hash(), cosignerData); SignedOrder memory signedOrder = @@ -408,12 +420,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { V3DutchOrder memory order = V3DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: wrongCosigner, - baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether), + baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether, 0), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper + address(tokenOut), 1 ether, 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper ), cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); order.cosignature = cosignOrder(order.hash(), cosignerData); SignedOrder memory signedOrder = @@ -435,12 +448,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { V3DutchOrder memory order = V3DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper), cosigner: wrongCosigner, - baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether), + baseInput: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether, 0), baseOutputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper + address(tokenOut), 1 ether, 1 ether, CurveBuilder.singlePointCurve(1, 0 ether), swapper ), cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); order.cosignature = bytes.concat(keccak256("invalidSignature"), keccak256("invalidSignature"), hex"33"); SignedOrder memory signedOrder = @@ -465,12 +479,14 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { tokenIn, inputAmount, CurveBuilder.singlePointCurve(1000, 0 - int256(inputAmount * 10 / 100)), - inputAmount * 110 / 100 + inputAmount * 110 / 100, + 0 ), outputs: OutputsBuilder.singleV3Dutch( address(tokenOut), outputAmount, - CurveBuilder.singlePointCurve(1000, int256(inputAmount * 10 / 100)), + outputAmount * 90 / 100, + CurveBuilder.singlePointCurve(1000, int256(outputAmount * 10 / 100)), address(swapper) ) }) @@ -498,11 +514,13 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { tokenIn, inputAmount, CurveBuilder.singlePointCurve(1000, 0 - int256(inputAmount * 10 / 100)), - inputAmount * 110 / 100 + inputAmount * 110 / 100, + 0 ), outputs: OutputsBuilder.singleV3Dutch( address(tokenOut), outputAmount, + outputAmount * 90 / 100, CurveBuilder.singlePointCurve(1000, int256(inputAmount * 10 / 100)), address(swapper) ) @@ -536,12 +554,14 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { tokenIn, inputAmount, CurveBuilder.singlePointCurve(1000, 0 - int256(inputAmount * 10 / 100)), - inputAmount * 110 / 100 + inputAmount * 110 / 100, + 0 ), outputs: OutputsBuilder.singleV3Dutch( address(tokenOut), outputAmount, - CurveBuilder.singlePointCurve(1000, int256(inputAmount * 10 / 100)), + outputAmount * 90 / 100, + CurveBuilder.singlePointCurve(1000, int256(outputAmount * 10 / 100)), address(swapper) ) }) @@ -567,9 +587,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: currentBlock + 100, deadline: currentBlock + 200, - input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(200, 1000), 2000), + input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(200, 1000), 2000, 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 0, CurveBuilder.singlePointCurve(200, -100), address(0) + address(tokenOut), 0, 0, CurveBuilder.singlePointCurve(200, -100), address(0) ) }) ); @@ -587,9 +607,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: currentBlock - 100, deadline: currentBlock + 200, - input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(100, 0), 0), + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(100, 0), 0, 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 2000, CurveBuilder.singlePointCurve(200, 1000), address(0) + address(tokenOut), 2000, 1000, CurveBuilder.singlePointCurve(200, 1000), address(0) ) }) ); @@ -607,9 +627,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: currentBlock - 200, deadline: currentBlock + 200, - input: V3DutchInput(tokenIn, 100, CurveBuilder.singlePointCurve(200, 100), 100), + input: V3DutchInput(tokenIn, 100, CurveBuilder.singlePointCurve(200, 100), 100, 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 2000, CurveBuilder.singlePointCurve(200, 1000), address(0) + address(tokenOut), 2000, 1000, CurveBuilder.singlePointCurve(200, 1000), address(0) ) }) ); @@ -627,9 +647,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: currentBlock - 100, deadline: currentBlock + 200, - input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(200, 1000), 1000), + input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(200, 1000), 1000, 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1000, CurveBuilder.singlePointCurve(200, 0), address(0) + address(tokenOut), 1000, 1000, CurveBuilder.singlePointCurve(200, 0), address(0) ) }) ); @@ -647,9 +667,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: currentBlock - 100, deadline: currentBlock + 200, - input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(100, 1000), 1000), + input: V3DutchInput(tokenIn, 1000, CurveBuilder.singlePointCurve(100, 1000), 1000, 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1000, CurveBuilder.singlePointCurve(100, 0), address(0) + address(tokenOut), 1000, 1000, CurveBuilder.singlePointCurve(100, 0), address(0) ) }) ); @@ -669,9 +689,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: 1659029740, deadline: 1659130540, - input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0), + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0, 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1000, CurveBuilder.singlePointCurve(relativeEndBlock, 100), address(0) + address(tokenOut), 1000, 900, CurveBuilder.singlePointCurve(relativeEndBlock, 100), address(0) ) }) ); @@ -696,7 +716,7 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: 1659029740, deadline: 1659130540, - input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0), + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0, 0), outputs: OutputsBuilder.multipleV3Dutch( address(tokenOut), Solarray.uint256s(1000, 10000, 2000), curves, address(0) ) @@ -721,9 +741,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: currentBlock, deadline: 1659130540, - input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0), + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0, 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1000, CurveBuilder.singlePointCurve(relativeEndBlock, 100), address(0) + address(tokenOut), 1000, 900, CurveBuilder.singlePointCurve(relativeEndBlock, 100), address(0) ) }) ); @@ -733,12 +753,12 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { assertEq(resolvedOrder.input.amount, 0); } - // At block 1659030395, output will still be 1000. One block later at 1659030396, + // 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 = 1659029740; - uint256 currentBlock = 1659030395; // 1659030395 - 1659029740 = 655 = 0.00999 * 65535 - uint16 relativeEndBlock = 65535; + uint256 startBlock = 0; + uint256 currentBlock = 99; // 1659030395 - 1659029740 = 655 = 0.00999 * 65535 + uint16 relativeEndBlock = 10000; vm.roll(currentBlock); SignedOrder memory order = generateOrder( @@ -746,9 +766,9 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: startBlock, deadline: 1659130540, - input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0), + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(relativeEndBlock, 0), 0, 0), outputs: OutputsBuilder.singleV3Dutch( - address(tokenOut), 1000, CurveBuilder.singlePointCurve(relativeEndBlock, 100), address(0) + address(tokenOut), 1000, 900, CurveBuilder.singlePointCurve(relativeEndBlock, 100), address(0) ) }) ); @@ -775,10 +795,11 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: uint256(currentBlock), startBlock: uint256(decayStartBlock), deadline: type(uint256).max, - input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(decayDuration, 0), 0), + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(decayDuration, 0), 0, 0), outputs: OutputsBuilder.singleV3Dutch( address(tokenOut), - uint256(startAmount), + startAmount, + startAmount, CurveBuilder.singlePointCurve(decayDuration, 0 - int256(decayAmount)), address(0) ) @@ -807,10 +828,11 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: uint256(currentBlock), startBlock: uint256(decayStartBlock), deadline: type(uint256).max, - input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(decayDuration, 0), 0), + input: V3DutchInput(tokenIn, 0, CurveBuilder.singlePointCurve(decayDuration, 0), 0, 0), outputs: OutputsBuilder.singleV3Dutch( address(tokenOut), - uint256(startAmount), + startAmount, + 0, CurveBuilder.singlePointCurve(decayDuration, int256(decayAmount)), address(0) ) @@ -842,8 +864,8 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: decayStartBlock, deadline: currentBlock + 400, - input: V3DutchInput(tokenIn, 1 ether, curve, 2 ether), - outputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1000, CurveBuilder.emptyCurve(), address(0)) + 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); @@ -901,43 +923,481 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { currentBlock: currentBlock, startBlock: decayStartBlock, deadline: currentBlock + 400, - input: V3DutchInput(tokenIn, 1 ether, CurveBuilder.emptyCurve(), 1 ether), - outputs: OutputsBuilder.singleV3Dutch(address(tokenOut), 1000, curve, address(0)) + 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 */ @@ -982,7 +1442,8 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest { baseInput: spec.input, baseOutputs: spec.outputs, cosignerData: cosignerData, - cosignature: bytes("") + cosignature: bytes(""), + startingBaseFee: block.basefee }); bytes32 orderHash = request.hash(); request.cosignature = cosignOrder(orderHash, cosignerData); diff --git a/test/util/OutputsBuilder.sol b/test/util/OutputsBuilder.sol index 9c6e4943..54451761 100644 --- a/test/util/OutputsBuilder.sol +++ b/test/util/OutputsBuilder.sol @@ -92,16 +92,22 @@ library OutputsBuilder { return result; } - function singleV3Dutch(address token, uint256 amount, NonlinearDutchDecay memory curve, address recipient) - internal - pure - returns (V3DutchOutput[] memory) - { + function singleV3Dutch(V3DutchOutput memory output) internal pure returns (V3DutchOutput[] memory) { V3DutchOutput[] memory result = new V3DutchOutput[](1); - result[0] = V3DutchOutput(token, amount, curve, recipient); + 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, @@ -110,7 +116,7 @@ library OutputsBuilder { ) 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); + result[i] = V3DutchOutput(token, amounts[i], curves[i], recipient, 0, 0); } return result; } From 19801bf5788ed87ce6a3d4ec50b909761b05e245 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Thu, 19 Sep 2024 15:08:56 -0400 Subject: [PATCH 24/24] Simplify NonlinearDecay logic --- .forge-snapshots/V3-DutchDecay.snap | 2 +- .forge-snapshots/V3-DutchDecayBounded.snap | 2 +- .../V3-DutchDecayFullyDecayed.snap | 2 +- .../V3-DutchDecayFullyDecayedNegative.snap | 2 +- .../V3-ExtendedMultiPointDutchDecay.snap | 2 +- .../V3-LocateCurvePositionMulti.snap | 2 +- .../V3-LocateCurvePositionSingle.snap | 2 +- .forge-snapshots/V3-MultiPointDutchDecay.snap | 2 +- src/lib/MathExt.sol | 8 -- src/lib/NonlinearDutchDecayLib.sol | 60 +++++++------ test/lib/MathExt.t.sol | 4 +- test/lib/NonLinearDutchDecayLib.t.sol | 89 +++++++++++-------- 12 files changed, 96 insertions(+), 81 deletions(-) diff --git a/.forge-snapshots/V3-DutchDecay.snap b/.forge-snapshots/V3-DutchDecay.snap index 6fcf259d..f7ed1714 100644 --- a/.forge-snapshots/V3-DutchDecay.snap +++ b/.forge-snapshots/V3-DutchDecay.snap @@ -1 +1 @@ -20288 \ No newline at end of file +20920 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayBounded.snap b/.forge-snapshots/V3-DutchDecayBounded.snap index 1f168c59..7862d759 100644 --- a/.forge-snapshots/V3-DutchDecayBounded.snap +++ b/.forge-snapshots/V3-DutchDecayBounded.snap @@ -1 +1 @@ -1199 \ No newline at end of file +1209 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap index 02c7f1f5..d20b1a84 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayed.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayed.snap @@ -1 +1 @@ -14988 \ No newline at end of file +11797 \ No newline at end of file diff --git a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap index 1c2819cd..fd311904 100644 --- a/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap +++ b/.forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap @@ -1 +1 @@ -14676 \ No newline at end of file +11485 \ No newline at end of file diff --git a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap index 3e62af5c..2fff3d5a 100644 --- a/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap @@ -1 +1 @@ -212154 \ No newline at end of file +209206 \ No newline at end of file diff --git a/.forge-snapshots/V3-LocateCurvePositionMulti.snap b/.forge-snapshots/V3-LocateCurvePositionMulti.snap index 06648cad..1f9b6835 100644 --- a/.forge-snapshots/V3-LocateCurvePositionMulti.snap +++ b/.forge-snapshots/V3-LocateCurvePositionMulti.snap @@ -1 +1 @@ -24086 \ No newline at end of file +40525 \ No newline at end of file diff --git a/.forge-snapshots/V3-LocateCurvePositionSingle.snap b/.forge-snapshots/V3-LocateCurvePositionSingle.snap index ca44913f..e274bed6 100644 --- a/.forge-snapshots/V3-LocateCurvePositionSingle.snap +++ b/.forge-snapshots/V3-LocateCurvePositionSingle.snap @@ -1 +1 @@ -8758 \ No newline at end of file +12623 \ No newline at end of file diff --git a/.forge-snapshots/V3-MultiPointDutchDecay.snap b/.forge-snapshots/V3-MultiPointDutchDecay.snap index 18d7df22..9015ad25 100644 --- a/.forge-snapshots/V3-MultiPointDutchDecay.snap +++ b/.forge-snapshots/V3-MultiPointDutchDecay.snap @@ -1 +1 @@ -53460 \ No newline at end of file +48878 \ No newline at end of file diff --git a/src/lib/MathExt.sol b/src/lib/MathExt.sol index e73c5164..02a2a786 100644 --- a/src/lib/MathExt.sol +++ b/src/lib/MathExt.sol @@ -3,24 +3,16 @@ pragma solidity ^0.8.0; import {SafeCast} from "openzeppelin-contracts/utils/math/SafeCast.sol"; import {Math} from "openzeppelin-contracts/utils/math/Math.sol"; -error NegativeUint(); - 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. - /// @custom:throws NegativeUint if the subtraction would result in a negative value. 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 { - // If b is positive, subtract it from a - if (a < uint256(b)) { - revert NegativeUint(); - } - return a - uint256(b); } } diff --git a/src/lib/NonlinearDutchDecayLib.sol b/src/lib/NonlinearDutchDecayLib.sol index 95347095..b979a883 100644 --- a/src/lib/NonlinearDutchDecayLib.sol +++ b/src/lib/NonlinearDutchDecayLib.sol @@ -38,47 +38,51 @@ library NonlinearDutchDecayLib { if (decayStartBlock >= block.number || curve.relativeAmounts.length == 0) { return startAmount.bound(minAmount, maxAmount); } - Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); + uint16 blockDelta = uint16(block.number - decayStartBlock); - int256 curveDelta; - // Special case for when we need to use the decayStartBlock (0) - if (relativeBlocks.getElement(0) > blockDelta) { - curveDelta = - DutchDecayLib.linearDecay(0, relativeBlocks.getElement(0), blockDelta, 0, curve.relativeAmounts[0]); - } else { - // the current pos is within or after the curve - (uint16 prev, uint16 next) = locateCurvePosition(curve, blockDelta); - // get decay of only the relative amounts - curveDelta = DutchDecayLib.linearDecay( - relativeBlocks.getElement(prev), - relativeBlocks.getElement(next), - blockDelta, - curve.relativeAmounts[prev], - curve.relativeAmounts[next] - ); - } + (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 using a binary search + /// @notice Locates the current position on the curve /// @param curve The curve to search /// @param currentRelativeBlock The current relative position - /// @return prev The relative block before the current position - /// @return next The relative block after the current 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 prev, uint16 next) + returns (uint16 startPoint, uint16 endPoint, int256 startAmount, int256 endAmount) { Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks); - uint16 curveLength = uint16(curve.relativeAmounts.length); - for (; next < curveLength; next++) { - if (relativeBlocks.getElement(next) >= currentRelativeBlock) { - return (prev, next); + // 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] + ); } - prev = next; } - return (next - 1, next - 1); + + 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 diff --git a/test/lib/MathExt.t.sol b/test/lib/MathExt.t.sol index 2d8765b4..b23d48de 100644 --- a/test/lib/MathExt.t.sol +++ b/test/lib/MathExt.t.sol @@ -4,7 +4,7 @@ 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, NegativeUint} from "../../src/lib/MathExt.sol"; +import {MathExt} from "../../src/lib/MathExt.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; contract MathExtTest is Test { @@ -32,7 +32,7 @@ contract MathExtTest is Test { } function testSubIntFromUintNegativeUint() public { - vm.expectRevert(NegativeUint.selector); + vm.expectRevert(); uint256(1).sub(int256(2)); } diff --git a/test/lib/NonLinearDutchDecayLib.t.sol b/test/lib/NonLinearDutchDecayLib.t.sol index da0f4440..c4217418 100644 --- a/test/lib/NonLinearDutchDecayLib.t.sol +++ b/test/lib/NonLinearDutchDecayLib.t.sol @@ -10,7 +10,6 @@ import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../../src/lib/V3 import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol"; import {ArrayBuilder} from "../util/ArrayBuilder.sol"; import {CurveBuilder} from "../util/CurveBuilder.sol"; -import {NegativeUint} from "../../src/lib/MathExt.sol"; /// @notice mock contract to test NonlinearDutchDecayLib functionality contract MockNonlinearDutchDecayLibContract { @@ -32,13 +31,18 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { NonlinearDutchDecay memory curve = CurveBuilder.singlePointCurve(1, 0); snapStart("V3-LocateCurvePositionSingle"); - (uint16 prev, uint16 next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 1); - assertEq(prev, 0); - assertEq(next, 0); - - (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 2); - assertEq(prev, 0); - assertEq(next, 0); + (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(); } @@ -56,33 +60,48 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot { snapStart("V3-LocateCurvePositionMulti"); // currentRelativeBlock shouldn't be less than the first block // but testing behavior anyways - (uint16 prev, uint16 next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 50); - assertEq(prev, 0); - assertEq(next, 0); - - (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 100); - assertEq(prev, 0); - assertEq(next, 0); - - (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 150); - assertEq(prev, 0); - assertEq(next, 1); - - (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 200); - assertEq(prev, 0); - assertEq(next, 1); - - (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 250); - assertEq(prev, 1); - assertEq(next, 2); - - (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 300); - assertEq(prev, 1); - assertEq(next, 2); - - (prev, next) = NonlinearDutchDecayLib.locateCurvePosition(curve, 350); - assertEq(prev, 2); - assertEq(next, 2); + (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(); }