Merge pull request #238 from propeller-heads/usx/tnl/ENG-4669-handle-approvals
feat: Handle approvals in UniswapXFiller
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
import "@openzeppelin/contracts/utils/Address.sol";
|
||||
import {TychoRouter} from "../TychoRouter.sol";
|
||||
|
||||
error UniswapXFiller__AddressZero();
|
||||
error UniswapXFiller__BatchExecutionNotSupported();
|
||||
@@ -19,6 +20,7 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
|
||||
// UniswapX V2DutchOrder Reactor
|
||||
IReactor public immutable reactor;
|
||||
address public immutable tychoRouter;
|
||||
address public immutable nativeAddress;
|
||||
|
||||
// keccak256("NAME_OF_ROLE") : save gas on deployment
|
||||
bytes32 public constant REACTOR_ROLE =
|
||||
@@ -30,7 +32,11 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
|
||||
address indexed token, uint256 amount, address indexed receiver
|
||||
);
|
||||
|
||||
constructor(address _tychoRouter, address _reactor) {
|
||||
constructor(
|
||||
address _tychoRouter,
|
||||
address _reactor,
|
||||
address _native_address
|
||||
) {
|
||||
if (_tychoRouter == address(0)) revert UniswapXFiller__AddressZero();
|
||||
if (_reactor == address(0)) revert UniswapXFiller__AddressZero();
|
||||
|
||||
@@ -38,6 +44,9 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
|
||||
_grantRole(REACTOR_ROLE, address(_reactor));
|
||||
tychoRouter = _tychoRouter;
|
||||
reactor = IReactor(_reactor);
|
||||
|
||||
// slither-disable-next-line missing-zero-check
|
||||
nativeAddress = _native_address;
|
||||
}
|
||||
|
||||
function execute(SignedOrder calldata order, bytes calldata callbackData)
|
||||
@@ -58,12 +67,20 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
|
||||
|
||||
ResolvedOrder memory order = resolvedOrders[0];
|
||||
|
||||
// TODO properly handle native in and out tokens
|
||||
uint256 ethValue = 0;
|
||||
bool tokenInApprovalNeeded = bool(uint8(callbackData[0]) == 1);
|
||||
bool tokenOutApprovalNeeded = bool(uint8(callbackData[1]) == 1);
|
||||
bytes calldata tychoCalldata = bytes(callbackData[2:]);
|
||||
|
||||
// The TychoRouter will take the input tokens from the filler
|
||||
if (tokenInApprovalNeeded) {
|
||||
// Native ETH input is not supported by UniswapX
|
||||
IERC20(order.input.token).forceApprove(
|
||||
tychoRouter, type(uint256).max
|
||||
);
|
||||
}
|
||||
|
||||
// slither-disable-next-line low-level-calls
|
||||
(bool success, bytes memory result) =
|
||||
tychoRouter.call{value: ethValue}(callbackData);
|
||||
(bool success, bytes memory result) = tychoRouter.call(tychoCalldata);
|
||||
|
||||
if (!success) {
|
||||
revert(
|
||||
@@ -75,10 +92,19 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
|
||||
);
|
||||
}
|
||||
|
||||
if (tokenOutApprovalNeeded) {
|
||||
// Multiple outputs are possible when taking fees - but token itself should
|
||||
// not change.
|
||||
IERC20 token = IERC20(order.outputs[0].token);
|
||||
OutputToken memory output = order.outputs[0];
|
||||
if (output.token != nativeAddress) {
|
||||
IERC20 token = IERC20(output.token);
|
||||
token.forceApprove(address(reactor), type(uint256).max);
|
||||
} else {
|
||||
// With native ETH - the filler is responsible for transferring back
|
||||
// to the reactor.
|
||||
Address.sendValue(payable(address(reactor)), output.amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
||||
|
||||
function fillerSetup() public {
|
||||
vm.startPrank(ADMIN);
|
||||
filler = new UniswapXFiller(tychoRouterAddr, REACTOR);
|
||||
filler = new UniswapXFiller(tychoRouterAddr, REACTOR, address(0));
|
||||
fillerAddr = address(filler);
|
||||
filler.grantRole(keccak256("EXECUTOR_ROLE"), EXECUTOR);
|
||||
vm.stopPrank();
|
||||
@@ -31,12 +31,12 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
||||
|
||||
function testTychoAddressZeroTychoRouter() public {
|
||||
vm.expectRevert(UniswapXFiller__AddressZero.selector);
|
||||
filler = new UniswapXFiller(address(0), REACTOR);
|
||||
filler = new UniswapXFiller(address(0), REACTOR, address(0));
|
||||
}
|
||||
|
||||
function testTychoAddressZeroReactor() public {
|
||||
vm.expectRevert(UniswapXFiller__AddressZero.selector);
|
||||
filler = new UniswapXFiller(tychoRouterAddr, address(0));
|
||||
filler = new UniswapXFiller(tychoRouterAddr, address(0), address(0));
|
||||
}
|
||||
|
||||
function testCallback() public {
|
||||
@@ -55,7 +55,7 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
||||
bytes memory swap =
|
||||
encodeSingleSwap(address(usv2Executor), protocolData);
|
||||
|
||||
bytes memory callbackData = abi.encodeWithSelector(
|
||||
bytes memory tychoRouterData = abi.encodeWithSelector(
|
||||
tychoRouter.singleSwap.selector,
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
@@ -68,6 +68,9 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
||||
swap
|
||||
);
|
||||
|
||||
bytes memory callbackData =
|
||||
abi.encodePacked(true, true, tychoRouterData);
|
||||
|
||||
deal(WETH_ADDR, address(filler), amountIn);
|
||||
vm.startPrank(address(filler));
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn);
|
||||
@@ -76,11 +79,11 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
||||
OutputToken[] memory outputs = new OutputToken[](1);
|
||||
outputs[0] = OutputToken({
|
||||
token: address(DAI_ADDR),
|
||||
// Irrelevant fields - we only need the token address for approval
|
||||
amount: 1847751195973566072891,
|
||||
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({
|
||||
info: OrderInfo({
|
||||
reactor: address(0),
|
||||
@@ -90,7 +93,12 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
||||
additionalValidationContract: address(0),
|
||||
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,
|
||||
sig: "",
|
||||
hash: ""
|
||||
@@ -133,7 +141,7 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
||||
bytes memory swap =
|
||||
encodeSingleSwap(address(usv2Executor), protocolData);
|
||||
|
||||
bytes memory callbackData = abi.encodeWithSelector(
|
||||
bytes memory tychoRouterData = abi.encodeWithSelector(
|
||||
tychoRouter.singleSwap.selector,
|
||||
amountIn,
|
||||
WBTC_ADDR,
|
||||
@@ -146,6 +154,12 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user