Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reactor unit tests #16

Closed
wants to merge 12 commits into from
1 change: 1 addition & 0 deletions .forge-snapshots/ExecuteSingle.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
108435
1 change: 1 addition & 0 deletions .forge-snapshots/ExecuteSingleWithRebate.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
107639
Original file line number Diff line number Diff line change
@@ -1 +1 @@
256168
256240
Original file line number Diff line number Diff line change
@@ -1 +1 @@
282546
282618
Original file line number Diff line number Diff line change
@@ -1 +1 @@
312232
312304
16 changes: 9 additions & 7 deletions src/reactors/RelayOrderReactor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,15 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe
ResolvedRelayOrder memory order = orders[i];
uint256 actionsLength = order.actions.length;
for (uint256 j = 0; j < actionsLength;) {
(address target, uint256 value, bytes memory data) =
abi.decode(order.actions[j], (address, uint256, bytes));
(bool success, bytes memory result) = target.call{value: value}(data);
if (!success) {
// bubble up all errors, including custom errors which are encoded like functions
assembly {
revert(add(result, 0x20), mload(result))
if (order.actions[j].length != 0) {
(address target, uint256 value, bytes memory data) =
abi.decode(order.actions[j], (address, uint256, bytes));
(bool success, bytes memory result) = target.call{value: value}(data);
if (!success) {
// bubble up all errors, including custom errors which are encoded like functions
assembly {
revert(add(result, 0x20), mload(result))
}
}
}
unchecked {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,11 @@ import {RelayOrderLib, RelayOrder} from "../../../src/lib/RelayOrderLib.sol";
import {RelayOrderReactor} from "../../../src/reactors/RelayOrderReactor.sol";
import {PermitExecutor} from "../../../src/sample-executors/PermitExecutor.sol";
import {MethodParameters, Interop} from "../util/Interop.sol";
import {UnsafeSignedMath} from "../util/UnsafeSignedMath.sol";

contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitSignature {
using stdJson for string;
using OrderInfoBuilder for OrderInfo;
using RelayOrderLib for RelayOrder;
using UnsafeSignedMath for int256;
using UnsafeSignedMath for uint256;

uint256 constant ONE = 10 ** 18;
uint256 constant USDC_ONE = 10 ** 6;
Expand Down
166 changes: 166 additions & 0 deletions test/foundry-tests/reactors/RelayOrderReactorTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {console2} from "forge-std/console2.sol";
import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol";
import {Test} from "forge-std/Test.sol";
import {OrderInfoBuilder} from "UniswapX/test/util/OrderInfoBuilder.sol";
import {OrderInfo, SignedOrder} from "UniswapX/src/base/ReactorStructs.sol";
import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
import {ArrayBuilder} from "UniswapX/test/util/ArrayBuilder.sol";
import {DeployPermit2} from "UniswapX/test/util/DeployPermit2.sol";
import {MockERC20} from "UniswapX/test/util/mock/MockERC20.sol";
import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol";
import {InputTokenWithRecipient, ResolvedRelayOrder} from "../../../src/base/ReactorStructs.sol";
import {RelayOrderLib, RelayOrder} from "../../../src/lib/RelayOrderLib.sol";
import {RelayOrderReactor} from "../../../src/reactors/RelayOrderReactor.sol";
import {PermitSignature} from "../util/PermitSignature.sol";
import {MockFillContract} from "../util/mock/MockExecutor.sol";

contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPermit2 {
using OrderInfoBuilder for OrderInfo;
using RelayOrderLib for RelayOrder;

MockERC20 tokenIn;
MockFillContract fillContract;
IPermit2 permit2;
RelayOrderReactor reactor;
uint256 swapperPrivateKey;
address swapper;

event Fill(bytes32 indexed orderHash, address indexed filler, address indexed swapper, uint256 nonce);
event Transfer(address indexed from, address indexed to, uint256 amount);

function setUp() public {
vm.chainId(1);

tokenIn = new MockERC20("Input", "IN", 18);

swapperPrivateKey = 0x12341234;
swapper = vm.addr(swapperPrivateKey);
permit2 = IPermit2(deployPermit2());
console2.logBytes32(permit2.DOMAIN_SEPARATOR());
console2.log(block.chainid);

reactor = new RelayOrderReactor(permit2);

fillContract = new MockFillContract(address(reactor));

// swapper approves permit2 to transfer tokens
tokenIn.forceApprove(swapper, address(permit2), type(uint256).max);
assertEq(tokenIn.allowance(swapper, address(permit2)), type(uint256).max);
}

/// @notice Create and return a basic single Relay order along with its signature, orderHash, and orderInfo
function createAndSignOrder(ResolvedRelayOrder memory resolvedOrder)
public
view
returns (SignedOrder memory signedOrder, bytes32 orderHash)
{
RelayOrder memory order = RelayOrder({
info: resolvedOrder.info,
decayStartTime: block.timestamp,
decayEndTime: resolvedOrder.info.deadline,
actions: resolvedOrder.actions,
inputs: resolvedOrder.inputs
});
orderHash = order.hash();
return (SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), orderHash);
}

/// @dev Test of a simple execute
/// @dev this order has no actions and its inputs decay from 0 ether to 1 ether
function testExecuteSingle() public {
uint256 inputAmount = 1 ether;
uint256 deadline = block.timestamp + 1000;

tokenIn.mint(address(swapper), uint256(inputAmount) * 100);
tokenIn.mint(address(fillContract), uint256(inputAmount) * 100);
tokenIn.forceApprove(swapper, address(permit2), inputAmount);

InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](1);
inputTokens[0] = InputTokenWithRecipient({
token: tokenIn,
amount: 0,
maxAmount: int256(inputAmount),
// sending to filler
recipient: address(0)
});

bytes[] memory actions = new bytes[](1);
actions[0] = hex"";

RelayOrder memory order = RelayOrder({
info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline),
decayStartTime: block.timestamp,
decayEndTime: deadline,
actions: actions,
inputs: inputTokens
});
bytes32 orderHash = order.hash();

SignedOrder memory signedOrder =
SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order));

uint256 fillContractTokenInBefore = tokenIn.balanceOf(address(fillContract));

// warp to precisely 25% way through the decay
// vm.warp(block.timestamp + 250);

vm.expectEmit(true, true, true, true, address(reactor));
emit Fill(orderHash, address(fillContract), swapper, order.info.nonce);
// execute order
snapStart("ExecuteSingleWithRebate");
fillContract.execute(signedOrder);
snapEnd();

assertEq(tokenIn.balanceOf(address(fillContract)), fillContractTokenInBefore + 250000000000000000);
}

/// @dev Test of a simple execute
/// @dev this order has no actions and its inputs decay from -1 ether to 1 ether
function testExecuteSingleWithRebate() public {
uint256 inputAmount = 1 ether;
uint256 deadline = block.timestamp + 1000;

tokenIn.mint(address(swapper), uint256(inputAmount) * 100);
tokenIn.mint(address(fillContract), uint256(inputAmount) * 100);
tokenIn.forceApprove(swapper, address(permit2), inputAmount);

InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](1);
inputTokens[0] = InputTokenWithRecipient({
token: tokenIn,
amount: -int256(inputAmount),
maxAmount: int256(inputAmount),
// sending to filler
recipient: address(0)
});

bytes[] memory actions = new bytes[](1);
actions[0] = "";

ResolvedRelayOrder memory order = ResolvedRelayOrder({
info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline),
inputs: inputTokens,
actions: actions,
sig: hex"00",
hash: bytes32(0)
});

(SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order);

// warp to precisely 25% way through the decay
vm.warp(block.timestamp + 250);

vm.expectEmit(true, true, true, false);
emit Transfer(address(fillContract), address(reactor), 500000000000000000);
vm.expectEmit(true, true, true, false);
emit Transfer(address(reactor), swapper, 500000000000000000);
vm.expectEmit(true, true, true, true, address(reactor));
emit Fill(orderHash, address(fillContract), swapper, order.info.nonce);
// execute order
snapStart("ExecuteSingleWithRebate");
fillContract.execute(signedOrder);
snapEnd();
}
}
16 changes: 16 additions & 0 deletions test/foundry-tests/util/DeployPermit2.sol

Large diffs are not rendered by default.

24 changes: 2 additions & 22 deletions test/foundry-tests/util/PermitSignature.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ contract PermitSignature is Test {

bytes32 internal constant TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)");

string constant TYPEHASH_STUB =
"PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,";

string public constant _PERMIT_BATCH_WITNESS_TRANSFER_TYPEHASH_STUB =
"PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,";

Expand Down Expand Up @@ -80,24 +77,7 @@ contract PermitSignature is Test {
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash);
sig = bytes.concat(r, s, bytes1(v));
}

function signOrder(
uint256 privateKey,
address permit2,
OrderInfo memory info,
address inputToken,
uint256 inputAmount,
bytes32 typeHash,
bytes32 orderHash
) internal view returns (bytes memory sig) {
ISignatureTransfer.PermitTransferFrom memory permit = ISignatureTransfer.PermitTransferFrom({
permitted: ISignatureTransfer.TokenPermissions({token: inputToken, amount: inputAmount}),
nonce: info.nonce,
deadline: info.deadline
});
return getPermitSignature(privateKey, permit2, permit, address(info.reactor), typeHash, orderHash);
}


function signOrder(
uint256 privateKey,
address permit2,
Expand All @@ -112,7 +92,7 @@ contract PermitSignature is Test {
if (inputs[i].amount > 0) {
permissions[i] = ISignatureTransfer.TokenPermissions({
token: address(inputs[i].token),
amount: uint256(inputs[i].amount)
amount: uint256(inputs[i].maxAmount)
});
}
}
Expand Down
16 changes: 0 additions & 16 deletions test/foundry-tests/util/UnsafeSignedMath.sol

This file was deleted.

20 changes: 0 additions & 20 deletions test/foundry-tests/util/mock/MockERC20.sol

This file was deleted.

45 changes: 45 additions & 0 deletions test/foundry-tests/util/mock/MockExecutor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol";
import {IReactor} from "UniswapX/src/interfaces/IReactor.sol";
import {IReactorCallback} from "UniswapX/src/interfaces/IReactorCallback.sol";
import {OutputToken, SignedOrder} from "UniswapX/src/base/ReactorStructs.sol";
import {ResolvedRelayOrder, InputTokenWithRecipient} from "../../../../src/base/ReactorStructs.sol";

contract MockFillContract {
using CurrencyLibrary for address;

IReactor immutable reactor;

constructor(address _reactor) {
reactor = IReactor(_reactor);
}

/// @notice assume that we already have all output tokens
function execute(SignedOrder calldata order) external {
reactor.executeWithCallback(order, hex"");
}

/// @notice assume that we already have all output tokens
function executeBatch(SignedOrder[] calldata orders) external {
reactor.executeBatchWithCallback(orders, hex"");
}

function reactorCallback(ResolvedRelayOrder[] memory resolvedOrders, bytes memory) external {
for (uint256 i = 0; i < resolvedOrders.length; i++) {
for (uint256 j = 0; j < resolvedOrders[i].inputs.length; j++) {
InputTokenWithRecipient memory input = resolvedOrders[i].inputs[j];
// only need to transfer to reactor if negative (swapper is owed)
if (input.amount < 0) {
if (address(input.token).isNative()) {
CurrencyLibrary.transferNative(address(reactor), uint256(-input.amount));
} else {
input.token.transfer(address(reactor), uint256(-input.amount));
}
}
}
}
}
}
Loading