feat: (WIP) Handle approvals in UniswapXFiller
Reasons for using regular approvals instead of permit2: - More simplicity, simpler setup - Unfortunately we won't be able to "expire" approvals, which is a risk we may be okay with taking - since one advantage of this is that we will be able save on gas by not approving the reactor every single time (is this our intention?) TODO: - Do we really need input tokens to pass through our router? - Handle native ETH (double check how this is transferred in the reactor)
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
import "@openzeppelin/contracts/access/AccessControl.sol";
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||||
import "@openzeppelin/contracts/utils/Address.sol";
|
import "@openzeppelin/contracts/utils/Address.sol";
|
||||||
|
import {TychoRouter} from "../TychoRouter.sol";
|
||||||
|
|
||||||
error UniswapXFiller__AddressZero();
|
error UniswapXFiller__AddressZero();
|
||||||
error UniswapXFiller__BatchExecutionNotSupported();
|
error UniswapXFiller__BatchExecutionNotSupported();
|
||||||
@@ -58,12 +59,34 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
|
|||||||
|
|
||||||
ResolvedOrder memory order = resolvedOrders[0];
|
ResolvedOrder memory order = resolvedOrders[0];
|
||||||
|
|
||||||
|
(
|
||||||
|
bool tokenInApprovalNeeded,
|
||||||
|
bool tokenOutApprovalNeeded,
|
||||||
|
bytes memory tychoCalldata
|
||||||
|
) = abi.decode(callbackData, (bool, bool, bytes));
|
||||||
|
|
||||||
|
// The TychoRouter will take the input tokens from the filler
|
||||||
|
if (tokenInApprovalNeeded) {
|
||||||
|
// TODO only if ERC20
|
||||||
|
IERC20(order.input.token).forceApprove(
|
||||||
|
tychoRouter, order.input.maxAmount
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// The filler will manually transfer into the TychoRouter
|
||||||
|
// Note: We are using the balance of our filler instead of the order
|
||||||
|
// input amount to avoid having to do a decay calculation in the filler.
|
||||||
|
IERC20 inputToken = IERC20(order.input.token);
|
||||||
|
inputToken.transfer(
|
||||||
|
tychoRouter, inputToken.balanceOf(address(this))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO properly handle native in and out tokens
|
// TODO properly handle native in and out tokens
|
||||||
uint256 ethValue = 0;
|
uint256 ethValue = 0;
|
||||||
|
|
||||||
// slither-disable-next-line low-level-calls
|
// slither-disable-next-line low-level-calls
|
||||||
(bool success, bytes memory result) =
|
(bool success, bytes memory result) =
|
||||||
tychoRouter.call{value: ethValue}(callbackData);
|
tychoRouter.call{value: ethValue}(tychoCalldata);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
revert(
|
revert(
|
||||||
@@ -75,10 +98,13 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiple outputs are possible when taking fees - but token itself should
|
if (tokenOutApprovalNeeded) {
|
||||||
// not change.
|
// Multiple outputs are possible when taking fees - but token itself should
|
||||||
IERC20 token = IERC20(order.outputs[0].token);
|
// not change.
|
||||||
token.forceApprove(address(reactor), type(uint256).max);
|
// TODO only if ERC20
|
||||||
|
IERC20 token = IERC20(order.outputs[0].token);
|
||||||
|
token.forceApprove(address(reactor), type(uint256).max);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
|||||||
bytes memory swap =
|
bytes memory swap =
|
||||||
encodeSingleSwap(address(usv2Executor), protocolData);
|
encodeSingleSwap(address(usv2Executor), protocolData);
|
||||||
|
|
||||||
bytes memory callbackData = abi.encodeWithSelector(
|
bytes memory tychoRouterData = abi.encodeWithSelector(
|
||||||
tychoRouter.singleSwap.selector,
|
tychoRouter.singleSwap.selector,
|
||||||
amountIn,
|
amountIn,
|
||||||
WETH_ADDR,
|
WETH_ADDR,
|
||||||
@@ -68,6 +68,8 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
|||||||
swap
|
swap
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bytes memory callbackData = abi.encode(true, true, tychoRouterData);
|
||||||
|
|
||||||
deal(WETH_ADDR, address(filler), amountIn);
|
deal(WETH_ADDR, address(filler), amountIn);
|
||||||
vm.startPrank(address(filler));
|
vm.startPrank(address(filler));
|
||||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn);
|
IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn);
|
||||||
@@ -76,11 +78,11 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
|||||||
OutputToken[] memory outputs = new OutputToken[](1);
|
OutputToken[] memory outputs = new OutputToken[](1);
|
||||||
outputs[0] = OutputToken({
|
outputs[0] = OutputToken({
|
||||||
token: address(DAI_ADDR),
|
token: address(DAI_ADDR),
|
||||||
// Irrelevant fields - we only need the token address for approval
|
|
||||||
amount: 1847751195973566072891,
|
amount: 1847751195973566072891,
|
||||||
recipient: BOB
|
recipient: BOB
|
||||||
});
|
});
|
||||||
// All irrelevant fields for this test - we only need the output token address
|
// Mostly irrelevant fields for this test - we only need token input and outputs
|
||||||
|
// info for the sake of testing.
|
||||||
orders[0] = ResolvedOrder({
|
orders[0] = ResolvedOrder({
|
||||||
info: OrderInfo({
|
info: OrderInfo({
|
||||||
reactor: address(0),
|
reactor: address(0),
|
||||||
@@ -90,7 +92,12 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
|||||||
additionalValidationContract: address(0),
|
additionalValidationContract: address(0),
|
||||||
additionalValidationData: ""
|
additionalValidationData: ""
|
||||||
}),
|
}),
|
||||||
input: InputToken({token: address(WETH_ADDR), amount: 0, maxAmount: 0}),
|
input: InputToken({
|
||||||
|
token: address(WETH_ADDR),
|
||||||
|
amount: amountIn,
|
||||||
|
// We need the proper maxAmount for our approval to work
|
||||||
|
maxAmount: amountIn
|
||||||
|
}),
|
||||||
outputs: outputs,
|
outputs: outputs,
|
||||||
sig: "",
|
sig: "",
|
||||||
hash: ""
|
hash: ""
|
||||||
@@ -133,7 +140,7 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
|||||||
bytes memory swap =
|
bytes memory swap =
|
||||||
encodeSingleSwap(address(usv2Executor), protocolData);
|
encodeSingleSwap(address(usv2Executor), protocolData);
|
||||||
|
|
||||||
bytes memory callbackData = abi.encodeWithSelector(
|
bytes memory tychoRouterData = abi.encodeWithSelector(
|
||||||
tychoRouter.singleSwap.selector,
|
tychoRouter.singleSwap.selector,
|
||||||
amountIn,
|
amountIn,
|
||||||
WBTC_ADDR,
|
WBTC_ADDR,
|
||||||
@@ -146,6 +153,12 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
|||||||
swap
|
swap
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bytes memory callbackData = abi.encode(
|
||||||
|
true, // tokenIn approval needed
|
||||||
|
true, // tokenOut approval needed
|
||||||
|
tychoRouterData
|
||||||
|
);
|
||||||
|
|
||||||
vm.startPrank(address(filler));
|
vm.startPrank(address(filler));
|
||||||
IERC20(WBTC_ADDR).approve(tychoRouterAddr, amountIn);
|
IERC20(WBTC_ADDR).approve(tychoRouterAddr, amountIn);
|
||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
|
|||||||
Reference in New Issue
Block a user