From 99090c613f84c7167d98d07fb91ebd8b043c9059 Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Tue, 19 Dec 2023 01:13:03 -0500 Subject: [PATCH] feat: refactor dutch reactor tests - Move common tests into a base test contract - inherit it from Dutch and ExclusiveDutch (dutchv2 coming later) - clean up test logic, use helpers and solarray --- .gitmodules | 3 + lib/solarray | 1 + src/reactors/ExclusiveDutchOrderReactor.sol | 5 +- test/base/BaseReactor.t.sol | 38 +- test/reactors/BaseDutchOrderReactor.t.sol | 431 ++++++++++++++++++ test/reactors/DutchOrderReactor.t.sol | 383 +--------------- .../reactors/ExclusiveDutchOrderReactor.t.sol | 26 +- test/util/OutputsBuilder.sol | 18 + 8 files changed, 500 insertions(+), 405 deletions(-) create mode 160000 lib/solarray create mode 100644 test/reactors/BaseDutchOrderReactor.t.sol diff --git a/.gitmodules b/.gitmodules index c6610a47..8dbd0cd0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "lib/permit2"] path = lib/permit2 url = https://github.com/Uniswap/permit2 +[submodule "lib/solarray"] + path = lib/solarray + url = https://github.com/evmcheb/solarray diff --git a/lib/solarray b/lib/solarray new file mode 160000 index 00000000..0625e7e4 --- /dev/null +++ b/lib/solarray @@ -0,0 +1 @@ +Subproject commit 0625e7e4369eb299753fcb90a3cd7ffb91e1b5bc diff --git a/src/reactors/ExclusiveDutchOrderReactor.sol b/src/reactors/ExclusiveDutchOrderReactor.sol index e70631a8..c4b7205a 100644 --- a/src/reactors/ExclusiveDutchOrderReactor.sol +++ b/src/reactors/ExclusiveDutchOrderReactor.sol @@ -20,9 +20,6 @@ contract ExclusiveDutchOrderReactor is BaseReactor { /// @notice thrown when an order's deadline is before its end time error DeadlineBeforeEndTime(); - /// @notice thrown when an order's end time is before its start time - error OrderEndTimeBeforeStartTime(); - /// @notice thrown when an order's inputs and outputs both decay error InputAndOutputDecay(); @@ -72,7 +69,7 @@ contract ExclusiveDutchOrderReactor is BaseReactor { } if (order.decayEndTime < order.decayStartTime) { - revert OrderEndTimeBeforeStartTime(); + revert DutchDecayLib.EndTimeBeforeStartTime(); } if (order.input.startAmount != order.input.endAmount) { diff --git a/test/base/BaseReactor.t.sol b/test/base/BaseReactor.t.sol index 18b79e1f..7af24d1c 100644 --- a/test/base/BaseReactor.t.sol +++ b/test/base/BaseReactor.t.sol @@ -88,7 +88,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Test of a simple execute - function testBaseExecute() public { + function test_base_execute() public { uint256 inputAmount = 1 ether; uint256 outputAmount = 1 ether; uint256 deadline = block.timestamp + 1000; @@ -128,7 +128,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Basic execute test with protocol fee, checks balance before and after - function testBaseExecuteWithFee() public { + function test_base_executeWithFee() public { uint256 inputAmount = 1 ether; uint256 outputAmount = 1 ether; uint256 deadline = block.timestamp + 1000; @@ -175,7 +175,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Basic execute test for native currency, checks balance before and after - function testBaseExecuteNativeOutput() public { + function test_base_executeNativeOutput() public { uint256 inputAmount = 1 ether; uint256 outputAmount = 1 ether; uint256 deadline = block.timestamp + 1000; @@ -213,7 +213,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Execute test with a succeeding validation contract - function testBaseExecuteValidationContract() public { + function test_base_executeValidationContract() public { uint256 inputAmount = 1 ether; uint256 outputAmount = 1 ether; uint256 deadline = block.timestamp + 1000; @@ -258,7 +258,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer /// @dev Basic batch execute test // Two orders: (inputs = 1, outputs = 2), (inputs = 2, outputs = 4) - function testBaseExecuteBatch() public { + function test_base_executeBatch() public { uint256 inputAmount = ONE; uint256 outputAmount = 2 * inputAmount; @@ -306,7 +306,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Basic batch execute test with native output - function testBaseExecuteBatchNativeOutput() public { + function test_base_executeBatchNativeOutput() public { uint256 inputAmount = ONE; uint256 outputAmount = 2 * inputAmount; @@ -356,7 +356,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer /// @dev Execute batch with multiple outputs /// Order 1: (inputs = 1, outputs = [2, 1]), /// Order 2: (inputs = 2, outputs = [3]) - function testBaseExecuteBatchMultipleOutputs() public { + function test_base_executeBatchMultipleOutputs() public { uint256[] memory inputAmounts = ArrayBuilder.fill(1, ONE).push(2 * ONE); uint256[] memory output1 = ArrayBuilder.fill(1, 2 * ONE).push(ONE); uint256[] memory output2 = ArrayBuilder.fill(1, 3 * ONE); @@ -408,7 +408,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer /// @dev Execute batch with multiple outputs /// Order 1: (inputs = 1, outputs = [2, 1]), /// Order 2: (inputs = 2, outputs = [3]) - function testBaseExecuteBatchMultipleOutputsDifferentTokens() public { + function test_base_executeBatchMultipleOutputsDifferentTokens() public { uint256[] memory output1 = ArrayBuilder.fill(1, 2 * ONE).push(ONE); uint256[] memory output2 = ArrayBuilder.fill(1, 3 * ONE).push(ONE); @@ -464,7 +464,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Base test preventing signatures from being reused - function testBaseExecuteSignatureReplay() public { + function test_base_executeSignatureReplay() public { // Seed both swapper and fillContract with enough tokens uint256 inputAmount = ONE; uint256 outputAmount = ONE * 2; @@ -512,7 +512,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Base test preventing nonce reuse - function testBaseNonceReuse() public { + function test_base_nonceReuse() public { uint256 inputAmount = ONE; uint256 outputAmount = ONE * 2; tokenIn.mint(address(swapper), inputAmount * 100); @@ -556,7 +556,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer /// @dev Test executing two orders on two reactors at once /// @dev executing the second order inside the callback of the first's execution - function testBaseExecuteTwoReactorsAtOnce() public { + function test_base_executeTwoReactorsAtOnce() public { BaseReactor otherReactor = createReactor(); MockFillContractDoubleExecution doubleExecutionFillContract = new MockFillContractDoubleExecution(address(reactor), address(otherReactor)); @@ -599,7 +599,9 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Basic execute test with protocol fee, checks balance before and after - function testBaseExecuteWithFee(uint128 inputAmount, uint128 outputAmount, uint256 deadline, uint8 feeBps) public { + function test_base_executeWithFee(uint128 inputAmount, uint128 outputAmount, uint256 deadline, uint8 feeBps) + public + { vm.assume(deadline > block.timestamp); vm.assume(feeBps <= 5); @@ -642,7 +644,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Basic execute test, checks balance before and after - function testBaseExecute(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { + function test_base_execute(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { vm.assume(deadline > block.timestamp); // Seed both swapper and fillContract with enough tokens (important for dutch order) tokenIn.mint(address(swapper), uint256(inputAmount) * 100); @@ -679,7 +681,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Execute with a succeeding validation contract - function testBaseExecuteValidationContract(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { + function test_base_executeValidationContract(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { vm.assume(deadline > block.timestamp); // Seed both swapper and fillContract with enough tokens (important for dutch order) tokenIn.mint(address(swapper), uint256(inputAmount) * 100); @@ -718,7 +720,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Execute with a failing validation contract - function testBaseExecuteValidationContractFail(uint128 inputAmount, uint128 outputAmount, uint256 deadline) + function test_base_executeValidationContractFail(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { vm.assume(deadline > block.timestamp); @@ -745,7 +747,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Execute with an invalid reactor - function testBaseExecuteInvalidReactor( + function test_base_executeInvalidReactor( address orderReactor, uint128 inputAmount, uint128 outputAmount, @@ -775,7 +777,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Execute with a deadline already passed - function testBaseExecuteDeadlinePassed(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { + function test_base_executeDeadlinePassed(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { vm.assume(deadline < block.timestamp); // Seed both swapper and fillContract with enough tokens (important for dutch order) tokenIn.mint(address(swapper), uint256(inputAmount) * 100); @@ -800,7 +802,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer } /// @dev Basic execute test for native currency, checks balance before and after - function testBaseExecuteNativeOutput(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { + function test_base_executeNativeOutput(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { vm.assume(deadline > block.timestamp); // Seed both swapper and fillContract with enough tokens (important for dutch order) tokenIn.mint(address(swapper), uint256(inputAmount) * 100); diff --git a/test/reactors/BaseDutchOrderReactor.t.sol b/test/reactors/BaseDutchOrderReactor.t.sol new file mode 100644 index 00000000..8987b2d7 --- /dev/null +++ b/test/reactors/BaseDutchOrderReactor.t.sol @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Solarray} from "solarray/Solarray.sol"; +import {Test} from "forge-std/Test.sol"; +import {DeployPermit2} from "../util/DeployPermit2.sol"; +import { + DutchOrderReactor, + DutchOrder, + ResolvedOrder, + DutchOutput, + DutchInput, + BaseReactor +} from "../../src/reactors/DutchOrderReactor.sol"; +import {OrderInfo, InputToken, OutputToken, SignedOrder} from "../../src/base/ReactorStructs.sol"; +import {OrderQuoter} from "../../src/lens/OrderQuoter.sol"; +import {DutchDecayLib} from "../../src/lib/DutchDecayLib.sol"; +import {CurrencyLibrary} from "../../src/lib/CurrencyLibrary.sol"; +import {NATIVE} from "../../src/lib/CurrencyLibrary.sol"; +import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; +import {MockDutchOrderReactor} from "../util/mock/MockDutchOrderReactor.sol"; +import {MockERC20} from "../util/mock/MockERC20.sol"; +import {DutchOrder, DutchOrderLib} from "../../src/lib/DutchOrderLib.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"; + +struct TestDutchOrderSpec { + uint256 currentTime; + uint256 startTime; + uint256 endTime; + uint256 deadline; + DutchInput input; + DutchOutput[] outputs; +} + +// Base suite of tests for Dutch decay functionality +// Intended for extension of reactors that take Dutch style orders +abstract contract BaseDutchOrderReactorTest is PermitSignature, DeployPermit2, BaseReactorTest { + using OrderInfoBuilder for OrderInfo; + using DutchOrderLib for DutchOrder; + + OrderQuoter quoter; + + constructor() { + quoter = new OrderQuoter(); + } + + /// @dev Create a signed order and return the order and orderHash + /// @param request Order to sign + function createAndSignDutchOrder(DutchOrder 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) private returns (SignedOrder memory order) { + vm.warp(spec.currentTime); + tokenIn.mint(address(swapper), uint256(spec.input.endAmount)); + tokenIn.forceApprove(swapper, address(permit2), spec.input.endAmount); + + DutchOrder memory request = DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withDeadline(spec.deadline).withSwapper(address(swapper)), + decayStartTime: spec.startTime, + decayEndTime: spec.endTime, + input: spec.input, + outputs: spec.outputs + }); + (order,) = createAndSignDutchOrder(request); + } + + function test_dutch_resolveOutputNotStarted() public { + uint256 currentTime = 1000; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime + 100, + endTime: currentTime + 200, + deadline: currentTime + 200, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, 2000, 1000, address(0)) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 2000); + assertEq(resolvedOrder.input.amount, 0); + } + + function test_dutch_resolveOutputHalfwayDecayed() public { + uint256 currentTime = 1000; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime - 100, + endTime: currentTime + 100, + deadline: currentTime + 200, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, 2000, 1000, address(0)) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1500); + assertEq(resolvedOrder.input.amount, 0); + } + + function test_dutch_resolveOutputFullyDecayed() public { + uint256 currentTime = 1000; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime - 200, + endTime: currentTime - 100, + deadline: currentTime + 200, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, 2000, 1000, address(0)) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + assertEq(resolvedOrder.input.amount, 0); + } + + function test_dutch_resolveInputNotStarted() public { + uint256 currentTime = 1000; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime + 100, + endTime: currentTime + 200, + deadline: currentTime + 200, + input: DutchInput(tokenIn, 1000, 2000), + outputs: OutputsBuilder.singleDutch(tokenOut, 0, 0, address(0)) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 1000); + } + + function test_dutch_resolveInputHalfwayDecayed() public { + uint256 currentTime = 1000; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime - 100, + endTime: currentTime + 100, + deadline: currentTime + 200, + input: DutchInput(tokenIn, 1000, 2000), + outputs: OutputsBuilder.singleDutch(tokenOut, 0, 0, address(0)) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 1500); + } + + function test_dutch_resolveInputFullyDecayed() public { + uint256 currentTime = 1000; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime - 200, + endTime: currentTime - 100, + deadline: currentTime + 200, + input: DutchInput(tokenIn, 1000, 2000), + outputs: OutputsBuilder.singleDutch(tokenOut, 0, 0, address(0)) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 0); + assertEq(resolvedOrder.input.amount, 2000); + } + + // 1000 - (1000-900) * (1659087340-1659029740) / (1659130540-1659029740) = 943 + function test_dutch_resolveEndTimeAfterNow() public { + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: 1659087340, + startTime: 1659029740, + endTime: 1659130540, + deadline: 1659130540, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, 1000, 900, address(0)) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 943); + assertEq(resolvedOrder.outputs.length, 1); + assertEq(resolvedOrder.input.amount, 0); + } + + // Test multiple dutch outputs get resolved correctly. Use same time points as + // testResolveEndTimeAfterNow(). + function test_dutch_resolveMultipleDutchOutputs() public { + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: 1659087340, + startTime: 1659029740, + endTime: 1659130540, + deadline: 1659130540, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.multipleDutch( + tokenOut, Solarray.uint256s(1000, 10000, 2000), Solarray.uint256s(900, 9000, 1000), address(0) + ) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs.length, 3); + assertEq(resolvedOrder.outputs[0].amount, 943); + assertEq(resolvedOrder.outputs[1].amount, 9429); + assertEq(resolvedOrder.outputs[2].amount, 1429); + assertEq(resolvedOrder.input.amount, 0); + } + + // Test that when decayStartTime = now, that the output = startAmount + function test_dutch_resolveStartTimeEqualsNow() public { + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: 1659029740, + startTime: 1659029740, + endTime: 1659130540, + deadline: 1659130540, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, 1000, 900, 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 time 1659030747, output will still be 1000. One second later at 1659030748, + // the first decay will occur and the output will be 999. + function test_dutch_resolveFirstDecay() public { + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: 1659030747, + startTime: 1659029740, + endTime: 1659130540, + deadline: 1659130540, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, 1000, 900, address(0)) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 1000); + + vm.warp(1659030748); + resolvedOrder = quoter.quote(order.order, order.sig); + assertEq(resolvedOrder.outputs[0].amount, 999); + } + + // startAmount is expected to always be greater than endAmount + // otherwise the order decays out of favor for the swapper + function test_dutch_resolveStartAmountLessThanEndAmount() public { + uint256 currentTime = 100; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime, + endTime: currentTime + 100, + deadline: currentTime + 100, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, 900, 1000, address(0)) + }) + ); + vm.expectRevert(DutchDecayLib.IncorrectAmounts.selector); + quoter.quote(order.order, order.sig); + } + + function test_dutch_validateDutchEndTimeBeforeStart() public { + uint256 currentTime = 100; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime + 100, + endTime: currentTime + 99, + deadline: currentTime + 100, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, 1000, 900, address(0)) + }) + ); + vm.expectRevert(DutchDecayLib.EndTimeBeforeStartTime.selector); + quoter.quote(order.order, order.sig); + } + + function test_dutch_validateDutchEndTimeEqualStart() public { + uint256 currentTime = 100; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime + 100, + endTime: currentTime + 100, + deadline: currentTime + 100, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, 1000, 900, address(0)) + }) + ); + vm.expectRevert(DutchDecayLib.EndTimeBeforeStartTime.selector); + quoter.quote(order.order, order.sig); + } + + function test_dutch_validateDutchEndTimeAfterStart() public { + uint256 currentTime = 100; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime + 100, + endTime: currentTime + 101, + deadline: currentTime + 200, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, 1000, 900, address(0)) + }) + ); + quoter.quote(order.order, order.sig); + } + + function test_dutch_validateEndTimeAfterDeadline() public { + uint256 currentTime = 100; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime, + endTime: currentTime + 100, + deadline: currentTime + 99, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, 1000, 900, address(0)) + }) + ); + vm.expectRevert(DutchOrderReactor.DeadlineBeforeEndTime.selector); + quoter.quote(order.order, order.sig); + } + + // The input decays, which means the outputs must not decay. In this test, the + // 2nd output decays, so revert with error InputAndOutputDecay(). + function test_dutch_validateInputAndOutputDecay() public { + uint256 currentTime = 100; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime, + endTime: currentTime + 100, + deadline: currentTime + 100, + input: DutchInput(tokenIn, 100, 110), + outputs: OutputsBuilder.multipleDutch( + tokenOut, Solarray.uint256s(1000, 1000), Solarray.uint256s(1000, 900), address(0) + ) + }) + ); + vm.expectRevert(DutchOrderReactor.InputAndOutputDecay.selector); + quoter.quote(order.order, order.sig); + } + + function test_dutch_validateInputDecayIncorrectAmounts() public { + uint256 currentTime = 100; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime, + endTime: currentTime + 100, + deadline: currentTime + 100, + input: DutchInput(tokenIn, 110, 100), + outputs: OutputsBuilder.singleDutch(tokenOut, 1000, 1000, address(0)) + }) + ); + vm.expectRevert(DutchDecayLib.IncorrectAmounts.selector); + quoter.quote(order.order, order.sig); + } + + function test_dutch_validateOutputDecayIncorrectAmounts() public { + uint256 currentTime = 100; + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: currentTime, + startTime: currentTime, + endTime: currentTime + 100, + deadline: currentTime + 100, + input: DutchInput(tokenIn, 100, 100), + outputs: OutputsBuilder.singleDutch(tokenOut, 1000, 1100, address(0)) + }) + ); + vm.expectRevert(DutchDecayLib.IncorrectAmounts.selector); + quoter.quote(order.order, order.sig); + } + + function test_dutch_fuzzDecayNeverOutOfBounds( + uint128 currentTime, + uint128 decayStartTime, + uint128 startAmount, + uint128 decayEndTime, + uint128 endAmount + ) public { + vm.assume(decayStartTime < decayEndTime); + vm.assume(startAmount > endAmount); + + SignedOrder memory order = generateOrder( + TestDutchOrderSpec({ + currentTime: uint256(currentTime), + startTime: uint256(decayStartTime), + endTime: uint256(decayEndTime), + deadline: type(uint256).max, + input: DutchInput(tokenIn, 0, 0), + outputs: OutputsBuilder.singleDutch(tokenOut, uint256(startAmount), uint256(endAmount), address(0)) + }) + ); + ResolvedOrder memory resolvedOrder = quoter.quote(order.order, order.sig); + assertLe(resolvedOrder.outputs[0].amount, startAmount); + assertGe(resolvedOrder.outputs[0].amount, endAmount); + } +} diff --git a/test/reactors/DutchOrderReactor.t.sol b/test/reactors/DutchOrderReactor.t.sol index a78b8b76..ddc537f3 100644 --- a/test/reactors/DutchOrderReactor.t.sol +++ b/test/reactors/DutchOrderReactor.t.sol @@ -25,389 +25,10 @@ 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"; - -// This suite of tests test validation and resolves. -contract DutchOrderReactorValidationTest is Test, DeployPermit2 { - using OrderInfoBuilder for OrderInfo; - - address constant PROTOCOL_FEE_OWNER = address(1); - - MockDutchOrderReactor reactor; - IPermit2 permit2; - - function setUp() public { - permit2 = IPermit2(deployPermit2()); - reactor = new MockDutchOrderReactor(permit2, PROTOCOL_FEE_OWNER); - } - - // 1000 - (1000-900) * (1659087340-1659029740) / (1659130540-1659029740) = 943 - function testResolveEndTimeAfterNow() public { - vm.warp(1659087340); - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 900, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659130540), - 1659029740, - 1659130540, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.outputs[0].amount, 943); - assertEq(resolvedOrder.outputs.length, 1); - assertEq(resolvedOrder.input.amount, 0); - assertEq(address(resolvedOrder.input.token), address(0)); - } - - // Test multiple dutch outputs get resolved correctly. Use same time points as - // testResolveEndTimeAfterNow(). - function testResolveMultipleDutchOutputs() public { - vm.warp(1659087340); - DutchOutput[] memory dutchOutputs = new DutchOutput[](3); - dutchOutputs[0] = DutchOutput(address(0), 1000, 900, address(0)); - dutchOutputs[1] = DutchOutput(address(0), 10000, 9000, address(0)); - dutchOutputs[2] = DutchOutput(address(0), 2000, 1000, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659130540), - 1659029740, - 1659130540, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.outputs.length, 3); - assertEq(resolvedOrder.outputs[0].amount, 943); - assertEq(resolvedOrder.outputs[1].amount, 9429); - assertEq(resolvedOrder.outputs[2].amount, 1429); - assertEq(resolvedOrder.input.amount, 0); - assertEq(address(resolvedOrder.input.token), address(0)); - } - - // Test that when decayStartTime = now, that the output = startAmount - function testResolveStartTimeEqualsNow() public { - vm.warp(1659029740); - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 900, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659130540), - 1659029740, - 1659130540, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.outputs[0].amount, 1000); - assertEq(resolvedOrder.outputs.length, 1); - assertEq(resolvedOrder.input.amount, 0); - assertEq(address(resolvedOrder.input.token), address(0)); - } - - // startAmount is expected to always be greater than endAmount - // otherwise the order decays out of favor for the swapper - function testStartAmountLessThanEndAmount() public { - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 900, 1000, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(block.timestamp + 100), - block.timestamp, - block.timestamp + 100, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - vm.expectRevert(DutchDecayLib.IncorrectAmounts.selector); - bytes memory sig = hex"1234"; - reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - } - - // At time 1659030747, output will still be 1000. One second later at 1659030748, - // the first decay will occur and the output will be 999. - function testResolveFirstDecay() public { - vm.warp(1659030747); - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 900, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659130540), - 1659029740, - 1659130540, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.outputs[0].amount, 1000); - - vm.warp(1659030748); - resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.outputs[0].amount, 999); - } - - function testValidateDutchEndTimeBeforeStart() public { - vm.expectRevert(DutchDecayLib.EndTimeBeforeStartTime.selector); - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 900, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659130540), - 1659130541, - 1659130540, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - } - - function testValidateDutchEndTimeEqualStart() public { - vm.expectRevert(DutchDecayLib.EndTimeBeforeStartTime.selector); - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 900, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659130540), - 1659130540, - 1659130540, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - } - - function testValidateDutchEndTimeAfterStart() public view { - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 900, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659130541), - 1659120540, - 1659130541, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - } - - function testValidateEndTimeAfterDeadline() public { - vm.expectRevert(DutchOrderReactor.DeadlineBeforeEndTime.selector); - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 900, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(100), - 50, - 101, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - } - - function testOutputDecaysCorrectlyWhenNowLtEndtimeLtDeadline() public { - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 900, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1000), - 50, - 100, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - vm.warp(75); - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.outputs.length, 1); - assertEq(resolvedOrder.outputs[0].amount, 950); - assertEq(resolvedOrder.input.amount, 0); - assertEq(address(resolvedOrder.input.token), address(0)); - } - - function testOutputDecaysCorrectlyWhenEndtimeLtNowLtDeadline() public { - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 900, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1000), - 50, - 100, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - vm.warp(200); - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.outputs.length, 1); - assertEq(resolvedOrder.outputs[0].amount, 900); - assertEq(resolvedOrder.input.amount, 0); - assertEq(address(resolvedOrder.input.token), address(0)); - } - - function testOutputDecaysCorrectlyWhenEndtimeEqNowLtDeadline() public { - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 900, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1000), - 50, - 100, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - vm.warp(100); - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.outputs.length, 1); - assertEq(resolvedOrder.outputs[0].amount, 900); - assertEq(resolvedOrder.input.amount, 0); - assertEq(address(resolvedOrder.input.token), address(0)); - } - - function testInputDecaysCorrectlyWhenNowLtEndtimeLtDeadline() public { - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 1000, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1000), - 50, - 100, - DutchInput(MockERC20(address(0)), 800, 1000), - dutchOutputs - ); - bytes memory sig = hex"1234"; - vm.warp(75); - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.outputs.length, 1); - assertEq(resolvedOrder.outputs[0].amount, 1000); - assertEq(resolvedOrder.input.amount, 900); - assertEq(address(resolvedOrder.input.token), address(0)); - } - - function testInputDecaysCorrectlyWhenEndtimeLtNowLtDeadline() public { - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 1000, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1000), - 50, - 100, - DutchInput(MockERC20(address(0)), 800, 1000), - dutchOutputs - ); - bytes memory sig = hex"1234"; - vm.warp(300); - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.outputs.length, 1); - assertEq(resolvedOrder.outputs[0].amount, 1000); - assertEq(resolvedOrder.input.amount, 1000); - assertEq(address(resolvedOrder.input.token), address(0)); - } - - function testDecayNeverOutOfBounds( - uint256 decayStartTime, - uint256 startAmount, - uint256 decayEndTime, - uint256 endAmount - ) public { - vm.assume(decayStartTime < decayEndTime); - vm.assume(startAmount > endAmount); - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), startAmount, endAmount, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(decayEndTime), - decayStartTime, - decayEndTime, - DutchInput(MockERC20(address(0)), 0, 0), - dutchOutputs - ); - bytes memory sig = hex"1234"; - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertLe(resolvedOrder.outputs[0].amount, startAmount); - assertGe(resolvedOrder.outputs[0].amount, endAmount); - } - - // The input decays, which means the outputs must not decay. In this test, the - // 2nd output decays, so revert with error InputAndOutputDecay(). - function testBothInputAndOutputDecay() public { - DutchOutput[] memory dutchOutputs = new DutchOutput[](2); - dutchOutputs[0] = DutchOutput(address(0), 1000, 1000, address(0)); - dutchOutputs[1] = DutchOutput(address(0), 1000, 900, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659130540), - 1659130500, - 1659130540, - DutchInput(MockERC20(address(0)), 100, 110), - dutchOutputs - ); - vm.expectRevert(DutchOrderReactor.InputAndOutputDecay.selector); - bytes memory sig = hex"1234"; - reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - } - - function testInputDecayIncorrectAmounts() public { - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 1000, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659130540), - 1659130500, - 1659130540, - DutchInput(MockERC20(address(0)), 110, 100), - dutchOutputs - ); - vm.expectRevert(DutchDecayLib.IncorrectAmounts.selector); - bytes memory sig = hex"1234"; - reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - } - - function testOutputDecayIncorrectAmounts() public { - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 1100, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659130540), - 1659130500, - 1659130540, - DutchInput(MockERC20(address(0)), 100, 100), - dutchOutputs - ); - vm.expectRevert(DutchDecayLib.IncorrectAmounts.selector); - bytes memory sig = hex"1234"; - reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - } - - function testInputDecayStartTimeAfterNow() public { - uint256 mockNow = 1659050541; - vm.warp(mockNow); - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 1000, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659130540), - mockNow + 1, - 1659130540, - DutchInput(MockERC20(address(0)), 2000, 2500), - dutchOutputs - ); - bytes memory sig = hex"1234"; - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.input.amount, 2000); - } - - // 2000+(2500-2000)*(20801/70901) = 2146 - function testInputDecayNowBetweenStartAndEnd() public { - uint256 mockNow = 1659050541; - vm.warp(mockNow); - DutchOutput[] memory dutchOutputs = new DutchOutput[](1); - dutchOutputs[0] = DutchOutput(address(0), 1000, 1000, address(0)); - DutchOrder memory dlo = DutchOrder( - OrderInfoBuilder.init(address(reactor)).withDeadline(1659100641), - 1659029740, - 1659100641, - DutchInput(MockERC20(address(0)), 2000, 2500), - dutchOutputs - ); - bytes memory sig = hex"1234"; - ResolvedOrder memory resolvedOrder = reactor.resolveOrder(SignedOrder(abi.encode(dlo), sig)); - assertEq(resolvedOrder.input.amount, 2146); - } -} +import {BaseDutchOrderReactorTest} from "./BaseDutchOrderReactor.t.sol"; // This suite of tests test execution with a mock fill contract. -contract DutchOrderReactorExecuteTest is PermitSignature, DeployPermit2, BaseReactorTest { +contract DutchOrderReactorExecuteTest is PermitSignature, DeployPermit2, BaseDutchOrderReactorTest { using OrderInfoBuilder for OrderInfo; using DutchOrderLib for DutchOrder; diff --git a/test/reactors/ExclusiveDutchOrderReactor.t.sol b/test/reactors/ExclusiveDutchOrderReactor.t.sol index 3c6e0ffc..070cc79a 100644 --- a/test/reactors/ExclusiveDutchOrderReactor.t.sol +++ b/test/reactors/ExclusiveDutchOrderReactor.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import {Test} from "forge-std/Test.sol"; import {DeployPermit2} from "../util/DeployPermit2.sol"; +import {DutchOrder} from "../../src/reactors/DutchOrderReactor.sol"; import { ExclusiveDutchOrderReactor, ExclusiveDutchOrder, @@ -22,9 +23,9 @@ 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 {BaseDutchOrderReactorTest} from "./BaseDutchOrderReactor.t.sol"; -contract ExclusiveDutchOrderReactorExecuteTest is PermitSignature, DeployPermit2, BaseReactorTest { +contract ExclusiveDutchOrderReactorTest is PermitSignature, DeployPermit2, BaseDutchOrderReactorTest { using OrderInfoBuilder for OrderInfo; using ExclusiveDutchOrderLib for ExclusiveDutchOrder; @@ -68,6 +69,27 @@ contract ExclusiveDutchOrderReactorExecuteTest is PermitSignature, DeployPermit2 return (SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), orderHash); } + /// @dev Create a signed order and return the order and orderHash + /// @param request Order to sign + function createAndSignDutchOrder(DutchOrder memory request) + public + view + override + returns (SignedOrder memory signedOrder, bytes32 orderHash) + { + ExclusiveDutchOrder memory order = ExclusiveDutchOrder({ + info: request.info, + decayStartTime: request.decayStartTime, + decayEndTime: request.decayEndTime, + exclusiveFiller: address(0), + exclusivityOverrideBps: 0, + input: request.input, + outputs: request.outputs + }); + orderHash = order.hash(); + return (SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), orderHash); + } + function testExecuteInputDecay() public { tokenIn.mint(address(swapper), 2 ether); tokenOut.mint(address(fillContract), 1 ether); diff --git a/test/util/OutputsBuilder.sol b/test/util/OutputsBuilder.sol index 7c86b4ae..aa77eb92 100644 --- a/test/util/OutputsBuilder.sol +++ b/test/util/OutputsBuilder.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {MockERC20} from "../util/mock/MockERC20.sol"; import {OutputToken} from "../../src/base/ReactorStructs.sol"; import {DutchOutput} from "../../src/reactors/DutchOrderReactor.sol"; @@ -24,6 +25,14 @@ library OutputsBuilder { return result; } + function singleDutch(MockERC20 token, uint256 startAmount, uint256 endAmount, address recipient) + internal + pure + returns (DutchOutput[] memory) + { + return OutputsBuilder.singleDutch(address(token), startAmount, endAmount, recipient); + } + function singleDutch(address token, uint256 startAmount, uint256 endAmount, address recipient) internal pure @@ -34,6 +43,15 @@ library OutputsBuilder { return result; } + function multipleDutch( + MockERC20 token, + uint256[] memory startAmounts, + uint256[] memory endAmounts, + address recipient + ) internal pure returns (DutchOutput[] memory) { + return OutputsBuilder.multipleDutch(address(token), startAmounts, endAmounts, recipient); + } + // Returns array of dutch outputs. and have same length. /// TODO: Support multiple tokens + recipients function multipleDutch(address token, uint256[] memory startAmounts, uint256[] memory endAmounts, address recipient)