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 unit tests for rebate as output design #28

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .forge-snapshots/ExecuteSingle.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
85705
1 change: 1 addition & 0 deletions .forge-snapshots/ExecuteSingleWithRebate.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
119339
2 changes: 1 addition & 1 deletion src/lib/Permit2Lib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ library Permit2Lib {
for (uint256 i = 0; i < order.inputs.length; i++) {
permissions[i] = ISignatureTransfer.TokenPermissions({
token: address(order.inputs[i].token),
amount: order.inputs[i].amount
amount: order.inputs[i].maxAmount
});
}
return ISignatureTransfer.PermitBatchTransferFrom({
Expand Down
4 changes: 2 additions & 2 deletions src/lib/RelayOrderLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ library RelayOrderLib {
"bytes[] actions,",
"RelayInput[] inputs,",
"RelayOutput[] outputs)",
INPUT_TOKEN_TYPE,
OrderInfoLib.ORDER_INFO_TYPE,
OUTPUT_TOKEN_TYPE_HASH
INPUT_TOKEN_TYPE,
OUTPUT_TOKEN_TYPE
);
bytes32 internal constant ORDER_TYPE_HASH = keccak256(ORDER_TYPE);

Expand Down
16 changes: 9 additions & 7 deletions src/reactors/RelayOrderReactor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,15 @@ contract RelayOrderReactor is IReactor, ReactorEvents, ReactorErrors, Reentrancy
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
164 changes: 164 additions & 0 deletions test/foundry-tests/reactors/RelayOrderReactorTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// 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 {ResolvedRelayOrder} from "../../../src/base/ReactorStructs.sol";
import {RelayOrderLib, RelayInput, RelayOutput, RelayOrder} from "../../../src/lib/RelayOrderLib.sol";
import {RelayOrderReactor} from "../../../src/reactors/RelayOrderReactor.sol";
import {PermitSignature} from "../util/PermitSignature.sol";
import {MockFillContractWithRebate} from "../util/mock/MockFillContractWithRebate.sol";

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

MockERC20 tokenIn;
MockFillContractWithRebate 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());

reactor = new RelayOrderReactor(permit2);

fillContract = new MockFillContractWithRebate(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);
}

/// @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);

RelayInput[] memory inputTokens = new RelayInput[](1);
inputTokens[0] = RelayInput({
token: tokenIn,
startAmount: 0,
endAmount: inputAmount,
decayStartTime: block.timestamp,
decayEndTime: deadline,
// 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),
actions: actions,
inputs: inputTokens,
outputs: new RelayOutput[](0)
});
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("ExecuteSingle");
fillContract.execute(signedOrder);
snapEnd();

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

/// @dev Test of a simple execute with rebate required
/// @dev this order has no actions and its inputs decay from 0 ether to 1 ether, and the outputs decay from 1 ether to 0
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);

RelayInput[] memory inputTokens = new RelayInput[](1);
inputTokens[0] = RelayInput({
token: tokenIn,
startAmount: 0,
endAmount: inputAmount,
decayStartTime: block.timestamp,
decayEndTime: deadline,
// sending to filler
recipient: address(0)
});

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

RelayOutput[] memory outputTokens = new RelayOutput[](1);
outputTokens[0] = RelayOutput({
token: address(tokenIn),
decayStartTime: block.timestamp,
decayEndTime: deadline,
startAmount: 1 ether,
endAmount: 0,
recipient: swapper
});

RelayOrder memory order = RelayOrder({
info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline),
inputs: inputTokens,
outputs: outputTokens,
actions: actions
});

bytes32 orderHash = order.hash();

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

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

vm.expectEmit(true, true, false, true);
emit Transfer(address(swapper), address(fillContract), 250000000000000000);
vm.expectEmit(true, true, false, true);
emit Transfer(address(fillContract), address(reactor), 750000000000000000);
vm.expectEmit(true, true, false, true);
emit Transfer(address(reactor), swapper, 750000000000000000);
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.

17 changes: 0 additions & 17 deletions test/foundry-tests/util/PermitSignature.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,6 @@ contract PermitSignature is Test {
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 Down