|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | +pragma solidity ^0.8.0; |
| 3 | + |
| 4 | +import {Owned} from "solmate/src/auth/Owned.sol"; |
| 5 | +import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; |
| 6 | +import {ERC20} from "solmate/src/tokens/ERC20.sol"; |
| 7 | +import {WETH} from "solmate/src/tokens/WETH.sol"; |
| 8 | +import {IReactorCallback} from "../interfaces/IReactorCallback.sol"; |
| 9 | +import {IReactor} from "../interfaces/IReactor.sol"; |
| 10 | +import {CurrencyLibrary} from "../lib/CurrencyLibrary.sol"; |
| 11 | +import {ResolvedOrder, SignedOrder} from "../base/ReactorStructs.sol"; |
| 12 | + |
| 13 | +/// @notice A fill contract that uses UniversalRouter to execute trades |
| 14 | +contract UniversalRouterExecutor is IReactorCallback, Owned { |
| 15 | + using SafeTransferLib for ERC20; |
| 16 | + using CurrencyLibrary for address; |
| 17 | + |
| 18 | + /// @notice thrown if reactorCallback is called with a non-whitelisted filler |
| 19 | + error CallerNotWhitelisted(); |
| 20 | + /// @notice thrown if reactorCallback is called by an address other than the reactor |
| 21 | + error MsgSenderNotReactor(); |
| 22 | + |
| 23 | + address private immutable universalRouter; |
| 24 | + address private immutable whitelistedCaller; |
| 25 | + IReactor private immutable reactor; |
| 26 | + |
| 27 | + modifier onlyWhitelistedCaller() { |
| 28 | + if (msg.sender != whitelistedCaller) { |
| 29 | + revert CallerNotWhitelisted(); |
| 30 | + } |
| 31 | + _; |
| 32 | + } |
| 33 | + |
| 34 | + modifier onlyReactor() { |
| 35 | + if (msg.sender != address(reactor)) { |
| 36 | + revert MsgSenderNotReactor(); |
| 37 | + } |
| 38 | + _; |
| 39 | + } |
| 40 | + |
| 41 | + constructor(address _whitelistedCaller, IReactor _reactor, address _owner, address _universalRouter) |
| 42 | + Owned(_owner) |
| 43 | + { |
| 44 | + whitelistedCaller = _whitelistedCaller; |
| 45 | + reactor = _reactor; |
| 46 | + universalRouter = _universalRouter; |
| 47 | + } |
| 48 | + |
| 49 | + /// @notice assume that we already have all output tokens |
| 50 | + function execute(SignedOrder calldata order, bytes calldata callbackData) external onlyWhitelistedCaller { |
| 51 | + reactor.executeWithCallback(order, callbackData); |
| 52 | + } |
| 53 | + |
| 54 | + /// @notice assume that we already have all output tokens |
| 55 | + function executeBatch(SignedOrder[] calldata orders, bytes calldata callbackData) external onlyWhitelistedCaller { |
| 56 | + reactor.executeBatchWithCallback(orders, callbackData); |
| 57 | + } |
| 58 | + |
| 59 | + /// @notice fill UniswapX orders using UniversalRouter |
| 60 | + /// @param callbackData It has the below encoded: |
| 61 | + /// address[] memory tokensToApproveForUniversalRouter: Max approve these tokens to universalRouter |
| 62 | + /// address[] memory tokensToApproveForReactor: Max approve these tokens to reactor |
| 63 | + /// bytes[] memory data: execution data |
| 64 | + function reactorCallback(ResolvedOrder[] calldata, bytes calldata callbackData) external onlyReactor { |
| 65 | + ( |
| 66 | + address[] memory tokensToApproveForUniversalRouter, |
| 67 | + address[] memory tokensToApproveForReactor, |
| 68 | + bytes memory data |
| 69 | + ) = abi.decode(callbackData, (address[], address[], bytes)); |
| 70 | + |
| 71 | + unchecked { |
| 72 | + for (uint256 i = 0; i < tokensToApproveForUniversalRouter.length; i++) { |
| 73 | + ERC20(tokensToApproveForUniversalRouter[i]).safeApprove(address(universalRouter), type(uint256).max); |
| 74 | + } |
| 75 | + |
| 76 | + for (uint256 i = 0; i < tokensToApproveForReactor.length; i++) { |
| 77 | + ERC20(tokensToApproveForReactor[i]).safeApprove(address(reactor), type(uint256).max); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + (bool success, bytes memory returnData) = universalRouter.call(data); |
| 82 | + if (!success) { |
| 83 | + assembly { |
| 84 | + revert(add(returnData, 32), mload(returnData)) |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + // transfer any native balance to the reactor |
| 89 | + // it will refund any excess |
| 90 | + if (address(this).balance > 0) { |
| 91 | + CurrencyLibrary.transferNative(address(reactor), address(this).balance); |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + /// @notice Transfer all ETH in this contract to the recipient. Can only be called by owner. |
| 96 | + /// @param recipient The recipient of the ETH |
| 97 | + function withdrawETH(address recipient) external onlyOwner { |
| 98 | + SafeTransferLib.safeTransferETH(recipient, address(this).balance); |
| 99 | + } |
| 100 | + |
| 101 | + /// @notice Necessary for this contract to receive ETH when calling unwrapWETH() |
| 102 | + receive() external payable {} |
| 103 | +} |
0 commit comments