Skip to content

Commit

Permalink
Add generic order type test suite (#86)
Browse files Browse the repository at this point in the history
* Add test for NonceReuse to DLO reactor test

* Ok have something, testExecute in baseTest passes, but only limit order specific rn

* remove comment

* Change to createAndSignOrder and use abiEncodedOrder in base tests

* Add signature replay base test

* Add inputAmount, outputAmount to createAndSignOrder

* Add base prefix to BaseReactorTest

* Remove extra createReactor call

* Add test for BaseExecuteBatch, and createAndSignBatchOrders

* Clean up and add comments

* Refactor createSginOrder to return SignedOrder and other resolved comments

* Remove virtual from base tests

* Allow specification of OrderInfo in generic func

* compiles!

* Update gas snaps

* Add NonceReuse base test

* Add comments

* Add comments

* Remove nonceReuse from dutchLimit

* forge fmt

* Revisions, and add ArrayBuilder to create 1d and 2d arays easier

* Add support in LimitOrderReactor.t.sol for batch orders with multiple outputs

* Direct taker macro (#77)

* continue

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* continue

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* add snapshot

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* continue

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* write testTwoOrders

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* continue

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* comment

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* write `testFillerHasInsufficientOutput`

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* fix merge main

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* write testSingleOrderWithFee

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* write testThreeOrdersWithFees()

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* remove code duplication

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* make permit2 an address that needs to be casted to appropriate interface

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* make DIRECT_TAKER_FILL constant

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* write testFillerLacksApproval()

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* make DIRECT_TAKER_FILL and internal variable

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* use SafeCast.toUint160

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* delete DirectTakerExecutor.t.sol

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

---------

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>

* udpate gas snap

* Remove redundant testExecutebatch

* remove old gas snap

* change func in lib type to internal

* remove unused initNested

* Remove console import and sign

* add .gitignore

---------

Signed-off-by: Allen Lin <allenlin1992@hotmail.com>
Co-authored-by: AzFlin <allenlin1992@hotmail.com>
  • Loading branch information
zhongeric and azflin committed Feb 2, 2023
1 parent bf6cce3 commit 2757030
Show file tree
Hide file tree
Showing 16 changed files with 476 additions and 133 deletions.
1 change: 0 additions & 1 deletion .forge-snapshots/DutchExecuteBatch.snap

This file was deleted.

1 change: 0 additions & 1 deletion .forge-snapshots/DutchExecuteSingle.snap

This file was deleted.

1 change: 1 addition & 0 deletions .forge-snapshots/DutchLimitOrderBaseExecuteBatch.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
183515
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
193742
1 change: 1 addition & 0 deletions .forge-snapshots/DutchLimitOrderBaseExecuteSingle.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
138142
1 change: 1 addition & 0 deletions .forge-snapshots/LimitOrderReactorBaseExecuteBatch.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
178542
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
187993
1 change: 1 addition & 0 deletions .forge-snapshots/LimitOrderReactorBaseExecuteSingle.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
135678
1 change: 1 addition & 0 deletions .forge-snapshots/LimitOrderReactorTestExecuteBatch.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
178449
1 change: 1 addition & 0 deletions .forge-snapshots/LimitOrderReactorTestExecuteSingle.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
135617
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ out/
/broadcast/*/31337/

.idea/
.env
267 changes: 267 additions & 0 deletions test/base/BaseReactor.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.16;

import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol";
import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol";
import {Test} from "forge-std/Test.sol";
import {BaseReactor} from "../../src/reactors/BaseReactor.sol";
import {ReactorEvents} from "../../src/base/ReactorEvents.sol";
import {SignedOrder, OrderInfo, InputToken, OutputToken} from "../../src/base/ReactorStructs.sol";
import {DeployPermit2} from "../util/DeployPermit2.sol";
import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol";
import {ArrayBuilder} from "../util/ArrayBuilder.sol";
import {MockERC20} from "../util/mock/MockERC20.sol";
import {MockFillContract} from "../util/mock/MockFillContract.sol";

abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test {
using OrderInfoBuilder for OrderInfo;
using ArrayBuilder for uint256[];
using ArrayBuilder for uint256[][];

uint256 constant ONE = 10 ** 18;

MockERC20 tokenIn;
MockERC20 tokenOut;
MockFillContract fillContract;
ISignatureTransfer permit2;
BaseReactor reactor;
uint256 makerPrivateKey;
address maker;

error InvalidNonce();
error InvalidSigner();

function name() public virtual returns (string memory) {}

/// @dev Virtual function to set up the test and state variables
function setUp() public virtual {}

/// @dev Virtual function to create the specific reactor in state
function createReactor() public virtual returns (BaseReactor) {}

/// @dev Create a signed order and return the order and orderHash
/// @param _info OrderInfo, uint256 inputAmount, uint256 outputAmount
function createAndSignOrder(OrderInfo memory _info, uint256 inputAmount, uint256 outputAmount)
public
virtual
returns (SignedOrder memory signedOrder, bytes32 orderHash)
{}

/// @dev Create many signed orders and return
/// @param _infos OrderInfo[], uint256[] inputAmounts, uint256[][] outputAmounts
/// supports orders with multiple outputs
function createAndSignBatchOrders(
OrderInfo[] memory _infos,
uint256[] memory inputAmounts,
uint256[][] memory outputAmounts
) public virtual returns (SignedOrder[] memory signedOrders, bytes32[] memory orderHashes) {}

/// @dev Basic execute test, checks balance before and after
function testBaseExecute() public {
// Seed both maker and fillContract with enough tokens (important for dutch order)
uint256 inputAmount = ONE;
uint256 outputAmount = ONE * 2;
tokenIn.mint(address(maker), inputAmount * 100);
tokenOut.mint(address(fillContract), outputAmount * 100);
tokenIn.forceApprove(maker, address(permit2), inputAmount);

OrderInfo memory orderInfo =
OrderInfoBuilder.init(address(reactor)).withOfferer(maker).withDeadline(block.timestamp + 100);
(SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(orderInfo, inputAmount, outputAmount);

(
uint256 makerInputBalanceStart,
uint256 fillContractInputBalanceStart,
uint256 makerOutputBalanceStart,
uint256 fillContractOutputBalanceStart
) = _checkpointBalances();

// TODO: expand to allow for custom fillData in 3rd param
vm.expectEmit(true, true, true, true, address(reactor));
emit Fill(orderHash, address(this), maker, orderInfo.nonce);
// execute order
snapStart(string.concat(name(), "BaseExecuteSingle"));
reactor.execute(signedOrder, address(fillContract), bytes(""));
snapEnd();

assertEq(tokenIn.balanceOf(address(maker)), makerInputBalanceStart - inputAmount);
assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount);
assertEq(tokenOut.balanceOf(address(maker)), makerOutputBalanceStart + outputAmount);
assertEq(tokenOut.balanceOf(address(fillContract)), fillContractOutputBalanceStart - outputAmount);
}

/// @dev Basic batch execute test
// Two orders: (inputs = 1, outputs = 2), (inputs = 2, outputs = 4)
function testBaseExecuteBatch() public {
uint256 inputAmount = ONE;
uint256 outputAmount = 2 * inputAmount;

tokenIn.mint(address(maker), inputAmount * 3);
tokenOut.mint(address(fillContract), 6 * 10 ** 18);
tokenIn.forceApprove(maker, address(permit2), type(uint256).max);

uint256[] memory inputAmounts = ArrayBuilder.fill(1, inputAmount).push(2 * inputAmount);
uint256[][] memory outputAmounts = ArrayBuilder.init(2, 1).set(0, ArrayBuilder.fill(1, outputAmount)).set(
1, ArrayBuilder.fill(1, 2 * outputAmount)
);

uint256 totalOutputAmount = outputAmounts.sum();
uint256 totalInputAmount = inputAmounts.sum();

OrderInfo[] memory infos = new OrderInfo[](2);
infos[0] =
OrderInfoBuilder.init(address(reactor)).withOfferer(maker).withDeadline(block.timestamp + 100).withNonce(0);
infos[1] =
OrderInfoBuilder.init(address(reactor)).withOfferer(maker).withDeadline(block.timestamp + 100).withNonce(1);

(SignedOrder[] memory signedOrders, bytes32[] memory orderHashes) =
createAndSignBatchOrders(infos, inputAmounts, outputAmounts);
vm.expectEmit(true, true, true, true);
emit Fill(orderHashes[0], address(this), maker, infos[0].nonce);
vm.expectEmit(true, true, true, true);
emit Fill(orderHashes[1], address(this), maker, infos[1].nonce);

snapStart(string.concat(name(), "BaseExecuteBatch"));
reactor.executeBatch(signedOrders, address(fillContract), bytes(""));
snapEnd();

assertEq(tokenOut.balanceOf(maker), totalOutputAmount);
assertEq(tokenIn.balanceOf(address(fillContract)), totalInputAmount);
}

/// @dev Execute batch with multiple outputs
/// Order 1: (inputs = 1, outputs = [2, 1]),
/// Order 2: (inputs = 2, outputs = [3])
function testBaseExecuteBatchMultipleOutputs() 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);
uint256[][] memory outputAmounts = ArrayBuilder.init(2, 1).set(0, output1).set(1, output2);

uint256 totalOutputAmount = outputAmounts.sum();
uint256 totalInputAmount = inputAmounts.sum();

tokenIn.mint(address(maker), totalInputAmount);
tokenOut.mint(address(fillContract), totalOutputAmount);
tokenIn.forceApprove(maker, address(permit2), type(uint256).max);

OrderInfo[] memory infos = new OrderInfo[](2);
infos[0] =
OrderInfoBuilder.init(address(reactor)).withOfferer(maker).withDeadline(block.timestamp + 100).withNonce(0);
infos[1] =
OrderInfoBuilder.init(address(reactor)).withOfferer(maker).withDeadline(block.timestamp + 100).withNonce(1);

(SignedOrder[] memory signedOrders, bytes32[] memory orderHashes) =
createAndSignBatchOrders(infos, inputAmounts, outputAmounts);
vm.expectEmit(true, true, true, true);
emit Fill(orderHashes[0], address(this), maker, infos[0].nonce);
vm.expectEmit(true, true, true, true);
emit Fill(orderHashes[1], address(this), maker, infos[1].nonce);

snapStart(string.concat(name(), "BaseExecuteBatchMultipleOutputs"));
reactor.executeBatch(signedOrders, address(fillContract), bytes(""));
snapEnd();

assertEq(tokenOut.balanceOf(maker), totalOutputAmount);
assertEq(tokenIn.balanceOf(address(fillContract)), totalInputAmount);
}

/// @dev Base test preventing signatures from being reused
function testBaseExecuteSignatureReplay() public {
// Seed both maker and fillContract with enough tokens
uint256 inputAmount = ONE;
uint256 outputAmount = ONE * 2;
tokenIn.mint(address(maker), inputAmount * 100);
tokenOut.mint(address(fillContract), outputAmount * 100);
tokenIn.forceApprove(maker, address(permit2), inputAmount);

OrderInfo memory orderInfo =
OrderInfoBuilder.init(address(reactor)).withOfferer(maker).withDeadline(block.timestamp + 100);
(SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(orderInfo, inputAmount, outputAmount);

(
uint256 makerInputBalanceStart,
uint256 fillContractInputBalanceStart,
uint256 makerOutputBalanceStart,
uint256 fillContractOutputBalanceStart
) = _checkpointBalances();

vm.expectEmit(true, true, true, true, address(reactor));
emit Fill(orderHash, address(this), maker, orderInfo.nonce);
reactor.execute(signedOrder, address(fillContract), bytes(""));

assertEq(tokenIn.balanceOf(address(maker)), makerInputBalanceStart - inputAmount);
assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount);
assertEq(tokenOut.balanceOf(address(maker)), makerOutputBalanceStart + outputAmount);
assertEq(tokenOut.balanceOf(address(fillContract)), fillContractOutputBalanceStart - outputAmount);

tokenIn.mint(address(maker), inputAmount);
tokenOut.mint(address(fillContract), outputAmount);
tokenIn.forceApprove(maker, address(permit2), inputAmount);

bytes memory oldSignature = signedOrder.sig;
// Create a new order, but use the previous signature
(signedOrder, orderHash) = createAndSignOrder(
OrderInfoBuilder.init(address(reactor)).withOfferer(maker).withDeadline(block.timestamp + 100).withNonce(1),
inputAmount,
outputAmount
);
signedOrder.sig = oldSignature;

vm.expectRevert(InvalidSigner.selector);
reactor.execute(signedOrder, address(fillContract), bytes(""));
}

/// @dev Base test preventing nonce reuse
function testBaseNonceReuse() public {
uint256 inputAmount = ONE;
uint256 outputAmount = ONE * 2;
tokenIn.mint(address(maker), inputAmount * 100);
tokenOut.mint(address(fillContract), outputAmount * 100);
// approve for 2 orders here
tokenIn.forceApprove(maker, address(permit2), inputAmount * 2);

OrderInfo memory orderInfo = OrderInfoBuilder.init(address(reactor)).withOfferer(maker).withDeadline(
block.timestamp + 100
).withNonce(123);
(SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(orderInfo, inputAmount, outputAmount);

(
uint256 makerInputBalanceStart,
uint256 fillContractInputBalanceStart,
uint256 makerOutputBalanceStart,
uint256 fillContractOutputBalanceStart
) = _checkpointBalances();

vm.expectEmit(true, true, true, true, address(reactor));
emit Fill(orderHash, address(this), maker, orderInfo.nonce);
reactor.execute(signedOrder, address(fillContract), bytes(""));

assertEq(tokenIn.balanceOf(address(maker)), makerInputBalanceStart - inputAmount);
assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount);
assertEq(tokenOut.balanceOf(address(maker)), makerOutputBalanceStart + outputAmount);
assertEq(tokenOut.balanceOf(address(fillContract)), fillContractOutputBalanceStart - outputAmount);

orderInfo = OrderInfoBuilder.init(address(reactor)).withOfferer(maker).withDeadline(block.timestamp + 100)
.withNonce(123);
(signedOrder, orderHash) = createAndSignOrder(orderInfo, inputAmount, outputAmount);
vm.expectRevert(InvalidNonce.selector);
reactor.execute(signedOrder, address(fillContract), bytes(""));
}

function _checkpointBalances()
internal
view
returns (
uint256 makerInputBalanceStart,
uint256 fillContractInputBalanceStart,
uint256 makerOutputBalanceStart,
uint256 fillContractOutputBalanceStart
)
{
makerInputBalanceStart = tokenIn.balanceOf(address(maker));
fillContractInputBalanceStart = tokenIn.balanceOf(address(fillContract));
makerOutputBalanceStart = tokenOut.balanceOf(address(maker));
fillContractOutputBalanceStart = tokenOut.balanceOf(address(fillContract));
}
}

0 comments on commit 2757030

Please sign in to comment.