302 lines
14 KiB
Solidity
302 lines
14 KiB
Solidity
// SPDX-License-Identifier: BUSL-1.1
|
|
pragma solidity ^0.8.26;
|
|
|
|
import "forge-std/Test.sol";
|
|
import "@src/uniswap_x/UniswapXFiller.sol";
|
|
import "../TychoRouterTestSetup.sol";
|
|
|
|
contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
|
address EXECUTOR = address(0xCe79b081c0c924cb67848723ed3057234d10FC6b);
|
|
address REACTOR = address(0x00000011F84B9aa48e5f8aA8B9897600006289Be);
|
|
|
|
UniswapXFiller filler;
|
|
address fillerAddr;
|
|
|
|
event CallbackVerifierSet(address indexed callbackVerifier);
|
|
event Withdrawal(
|
|
address indexed token, uint256 amount, address indexed receiver
|
|
);
|
|
|
|
function getForkBlock() public pure override returns (uint256) {
|
|
return 22880493;
|
|
}
|
|
|
|
function fillerSetup() public {
|
|
vm.startPrank(ADMIN);
|
|
filler = new UniswapXFiller(tychoRouterAddr, REACTOR, address(0));
|
|
fillerAddr = address(filler);
|
|
filler.grantRole(keccak256("EXECUTOR_ROLE"), EXECUTOR);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testTychoAddressZeroTychoRouter() public {
|
|
vm.expectRevert(UniswapXFiller__AddressZero.selector);
|
|
filler = new UniswapXFiller(address(0), REACTOR, address(0));
|
|
}
|
|
|
|
function testTychoAddressZeroReactor() public {
|
|
vm.expectRevert(UniswapXFiller__AddressZero.selector);
|
|
filler = new UniswapXFiller(tychoRouterAddr, address(0), address(0));
|
|
}
|
|
|
|
function testCallback() public {
|
|
fillerSetup();
|
|
uint256 amountIn = 10 ** 18;
|
|
uint256 amountOut = 1847751195973566072891;
|
|
bool zeroForOne = false;
|
|
bytes memory protocolData = abi.encodePacked(
|
|
WETH_ADDR,
|
|
WETH_DAI_POOL,
|
|
address(filler),
|
|
zeroForOne,
|
|
RestrictTransferFrom.TransferType.TransferFrom
|
|
);
|
|
|
|
bytes memory swap =
|
|
encodeSingleSwap(address(usv2Executor), protocolData);
|
|
|
|
bytes memory tychoRouterData = abi.encodeWithSelector(
|
|
tychoRouter.singleSwap.selector,
|
|
amountIn,
|
|
WETH_ADDR,
|
|
DAI_ADDR,
|
|
2008817438608734439722,
|
|
false,
|
|
false,
|
|
address(filler),
|
|
true,
|
|
swap
|
|
);
|
|
|
|
bytes memory callbackData =
|
|
abi.encodePacked(true, true, tychoRouterData);
|
|
|
|
deal(WETH_ADDR, address(filler), amountIn);
|
|
|
|
ResolvedOrder[] memory orders = new ResolvedOrder[](1);
|
|
OutputToken[] memory outputs = new OutputToken[](1);
|
|
outputs[0] = OutputToken({
|
|
token: address(DAI_ADDR),
|
|
amount: 1847751195973566072891,
|
|
recipient: BOB
|
|
});
|
|
// Irrelevant fields for this test - we only need token output
|
|
// info for the sake of testing.
|
|
orders[0] = ResolvedOrder({
|
|
info: OrderInfo({
|
|
reactor: address(0),
|
|
swapper: address(0),
|
|
nonce: 0,
|
|
deadline: 0,
|
|
additionalValidationContract: address(0),
|
|
additionalValidationData: ""
|
|
}),
|
|
input: InputToken({
|
|
token: address(WETH_ADDR), amount: amountIn, maxAmount: amountIn
|
|
}),
|
|
outputs: outputs,
|
|
sig: "",
|
|
hash: ""
|
|
});
|
|
|
|
vm.startPrank(REACTOR);
|
|
filler.reactorCallback(orders, callbackData);
|
|
vm.stopPrank();
|
|
|
|
// Check that the funds are in the filler at the end of the function call
|
|
uint256 finalBalance = IERC20(DAI_ADDR).balanceOf(address(filler));
|
|
assertGe(finalBalance, amountOut);
|
|
|
|
// Check that the proper approval was set
|
|
vm.startPrank(REACTOR);
|
|
IERC20(DAI_ADDR).transferFrom(address(filler), BOB, amountOut);
|
|
vm.stopPrank();
|
|
assertGe(IERC20(DAI_ADDR).balanceOf(BOB), amountOut);
|
|
}
|
|
|
|
function testExecuteIntegration() public {
|
|
fillerSetup();
|
|
|
|
// Set to time with no more penalty for not being exclusive filler
|
|
vm.warp(1752050415);
|
|
|
|
deal(
|
|
DAI_ADDR,
|
|
address(0xD213e6F6dCB2DBaC03FA28b893F6dA1BD822e852),
|
|
2000 ether
|
|
);
|
|
|
|
uint256 amountIn = 2000000000000000000000;
|
|
|
|
vm.startPrank(address(0xD213e6F6dCB2DBaC03FA28b893F6dA1BD822e852));
|
|
// Approve Permit2
|
|
IERC20(DAI_ADDR)
|
|
.approve(
|
|
address(0x000000000022D473030F116dDEE9F6B43aC78BA3), amountIn
|
|
);
|
|
vm.stopPrank();
|
|
|
|
// Tx 0x005d7b150017ba1b59d2f99395ccae7bda9b739938ade4e509817e32760aaf9d
|
|
// Calldata generated using rust test `test_sequential_swap_usx`
|
|
|
|
SignedOrder memory order = SignedOrder({
|
|
order: hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001000000000000000000000000004449cd34d1eb1fedcf02a1be3834ffde8e6a61800000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000006c6b935b8bbd40000000000000000000000000000000000000000000000000006c6b935b8bbd40000000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000011f84b9aa48e5f8aa8b9897600006289be000000000000000000000000d213e6f6dcb2dbac03fa28b893f6da1bd822e8520468320351debb1ddbfb032a239d699e3d54e3ce2b6e1037cd836a784c80b60100000000000000000000000000000000000000000000000000000000686e2bf9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000076f9f4870000000000000000000000000000000000000000000000000000000076566300000000000000000000000000d213e6f6dcb2dbac03fa28b893f6da1bd822e85200000000000000000000000000000000000000000000000000000000686e2aee00000000000000000000000000000000000000000000000000000000686e2b2a000000000000000000000000ce79b081c0c924cb67848723ed3057234d10fc6b0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000007727b5f40000000000000000000000000000000000000000000000000000000000000041a2d261cd4c8930428260f18b55e3036024bac68d58cb2ee6161e6395b0984b827104158713d44ddc4e14d852b48d93d95a4e60b8d5be1ef431c1e82d2f76a4111b00000000000000000000000000000000000000000000000000000000000000",
|
|
sig: hex"f4cc5734820e4ee08519045c83a25b75687756053b3d6c0fda2141380dfa6ef17b40f64d9279f237e96982c6ba53a202e01a4358fd66e027c9bdf200d5626f441c"
|
|
});
|
|
|
|
bytes memory callbackData =
|
|
loadCallDataFromFile("test_sequential_swap_usx");
|
|
|
|
vm.startPrank(EXECUTOR);
|
|
filler.execute(order, callbackData);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testExecute() public {
|
|
fillerSetup();
|
|
|
|
// Set to time with no more penalty for not being exclusive filler
|
|
vm.warp(1752050415);
|
|
|
|
// tx: 0x005d7b150017ba1b59d2f99395ccae7bda9b739938ade4e509817e32760aaf9d
|
|
// DAI ──> USDT
|
|
SignedOrder memory order = SignedOrder({
|
|
order: hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001000000000000000000000000004449cd34d1eb1fedcf02a1be3834ffde8e6a61800000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000006c6b935b8bbd40000000000000000000000000000000000000000000000000006c6b935b8bbd40000000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000011f84b9aa48e5f8aa8b9897600006289be000000000000000000000000d213e6f6dcb2dbac03fa28b893f6da1bd822e8520468320351debb1ddbfb032a239d699e3d54e3ce2b6e1037cd836a784c80b60100000000000000000000000000000000000000000000000000000000686e2bf9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000076f9f4870000000000000000000000000000000000000000000000000000000076566300000000000000000000000000d213e6f6dcb2dbac03fa28b893f6da1bd822e85200000000000000000000000000000000000000000000000000000000686e2aee00000000000000000000000000000000000000000000000000000000686e2b2a000000000000000000000000ce79b081c0c924cb67848723ed3057234d10fc6b0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000007727b5f40000000000000000000000000000000000000000000000000000000000000041a2d261cd4c8930428260f18b55e3036024bac68d58cb2ee6161e6395b0984b827104158713d44ddc4e14d852b48d93d95a4e60b8d5be1ef431c1e82d2f76a4111b00000000000000000000000000000000000000000000000000000000000000",
|
|
sig: hex"f4cc5734820e4ee08519045c83a25b75687756053b3d6c0fda2141380dfa6ef17b40f64d9279f237e96982c6ba53a202e01a4358fd66e027c9bdf200d5626f441c"
|
|
});
|
|
|
|
uint256 amountIn = 2000000000000000000000;
|
|
bool zeroForOne = true;
|
|
uint24 fee = 100;
|
|
bytes memory protocolData = abi.encodePacked(
|
|
DAI_ADDR,
|
|
USDT_ADDR,
|
|
fee,
|
|
fillerAddr,
|
|
DAI_USDT_USV3,
|
|
zeroForOne,
|
|
RestrictTransferFrom.TransferType.TransferFrom
|
|
);
|
|
|
|
bytes memory swap =
|
|
encodeSingleSwap(address(usv3Executor), protocolData);
|
|
|
|
bytes memory tychoRouterData = abi.encodeWithSelector(
|
|
tychoRouter.singleSwap.selector,
|
|
amountIn,
|
|
DAI_ADDR,
|
|
USDT_ADDR,
|
|
1,
|
|
false,
|
|
false,
|
|
fillerAddr,
|
|
true,
|
|
swap
|
|
);
|
|
|
|
bytes memory callbackData = abi.encodePacked(
|
|
true, // tokenIn approval needed
|
|
true, // tokenOut approval needed
|
|
tychoRouterData
|
|
);
|
|
|
|
vm.startPrank(address(filler));
|
|
IERC20(WBTC_ADDR).approve(tychoRouterAddr, amountIn);
|
|
vm.stopPrank();
|
|
|
|
vm.startPrank(EXECUTOR);
|
|
filler.execute(order, callbackData);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdrawNative() public {
|
|
fillerSetup();
|
|
vm.startPrank(ADMIN);
|
|
// Send 100 ether to filler
|
|
assertEq(fillerAddr.balance, 0);
|
|
assertEq(ADMIN.balance, 0);
|
|
vm.deal(fillerAddr, 100 ether);
|
|
vm.expectEmit();
|
|
emit Withdrawal(address(0), 100 ether, ADMIN);
|
|
filler.withdrawNative(ADMIN);
|
|
assertEq(fillerAddr.balance, 0);
|
|
assertEq(ADMIN.balance, 100 ether);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdrawNativeAddressZero() public {
|
|
fillerSetup();
|
|
vm.deal(fillerAddr, 100 ether);
|
|
vm.startPrank(ADMIN);
|
|
vm.expectRevert(UniswapXFiller__AddressZero.selector);
|
|
filler.withdrawNative(address(0));
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdrawNativeMissingRole() public {
|
|
fillerSetup();
|
|
vm.deal(fillerAddr, 100 ether);
|
|
// Not role ADMIN
|
|
vm.startPrank(BOB);
|
|
vm.expectRevert();
|
|
filler.withdrawNative(ADMIN);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdrawERC20Tokens() public {
|
|
fillerSetup();
|
|
|
|
IERC20[] memory tokens = new IERC20[](2);
|
|
tokens[0] = IERC20(WETH_ADDR);
|
|
tokens[1] = IERC20(USDC_ADDR);
|
|
for (uint256 i = 0; i < tokens.length; i++) {
|
|
deal(address(tokens[i]), fillerAddr, 100 ether);
|
|
}
|
|
|
|
vm.startPrank(ADMIN);
|
|
filler.withdraw(tokens, ADMIN);
|
|
|
|
// Check balances after withdrawing
|
|
for (uint256 i = 0; i < tokens.length; i++) {
|
|
// slither-disable-next-line calls-loop
|
|
assertEq(tokens[i].balanceOf(fillerAddr), 0);
|
|
// slither-disable-next-line calls-loop
|
|
assertEq(tokens[i].balanceOf(ADMIN), 100 ether);
|
|
}
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdrawERC20TokensAddressZero() public {
|
|
fillerSetup();
|
|
|
|
IERC20[] memory tokens = new IERC20[](2);
|
|
tokens[0] = IERC20(WETH_ADDR);
|
|
tokens[1] = IERC20(USDC_ADDR);
|
|
for (uint256 i = 0; i < tokens.length; i++) {
|
|
deal(address(tokens[i]), fillerAddr, 100 ether);
|
|
}
|
|
|
|
vm.startPrank(ADMIN);
|
|
vm.expectRevert(UniswapXFiller__AddressZero.selector);
|
|
filler.withdraw(tokens, address(0));
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdrawERC20TokensAddressMissingRole() public {
|
|
fillerSetup();
|
|
|
|
IERC20[] memory tokens = new IERC20[](2);
|
|
tokens[0] = IERC20(WETH_ADDR);
|
|
tokens[1] = IERC20(USDC_ADDR);
|
|
for (uint256 i = 0; i < tokens.length; i++) {
|
|
deal(address(tokens[i]), fillerAddr, 100 ether);
|
|
}
|
|
|
|
// Not role ADMIN
|
|
vm.startPrank(BOB);
|
|
vm.expectRevert();
|
|
filler.withdraw(tokens, ADMIN);
|
|
vm.stopPrank();
|
|
}
|
|
}
|