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

example multifiller executor #272

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
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
122 changes: 122 additions & 0 deletions src/sample-executors/MultiFillerSwapRouter02Executor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {Owned} from "solmate/src/auth/Owned.sol";
import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {WETH} from "solmate/src/tokens/WETH.sol";
import {IReactorCallback} from "../interfaces/IReactorCallback.sol";
import {IReactor} from "../interfaces/IReactor.sol";
import {CurrencyLibrary} from "../lib/CurrencyLibrary.sol";
import {ResolvedOrder, SignedOrder} from "../base/ReactorStructs.sol";
import {ISwapRouter02} from "../external/ISwapRouter02.sol";

/// @notice A fill contract that uses SwapRouter02 to execute trades
contract SwapRouter02Executor is IReactorCallback, Owned {
using SafeTransferLib for ERC20;
using CurrencyLibrary for address;

/// @notice thrown if reactorCallback is called with a non-whitelisted filler
error CallerNotWhitelisted();
/// @notice thrown if reactorCallback is called by an address other than the reactor
error MsgSenderNotReactor();

ISwapRouter02 private immutable swapRouter02;
mapping (address => bool) whitelistedCallers;
IReactor private immutable reactor;
WETH private immutable weth;

modifier onlyWhitelistedCaller() {
if (whitelistedCallers[msg.sender] == false) {
revert CallerNotWhitelisted();
}
_;
}

modifier onlyReactor() {
if (msg.sender != address(reactor)) {
revert MsgSenderNotReactor();
}
_;
}

constructor(address[] memory _whitelistedCallers, IReactor _reactor, address _owner, ISwapRouter02 _swapRouter02)
Owned(_owner)
{
for (uint256 i = 0; i < _whitelistedCallers.length; i++) {
whitelistedCallers[_whitelistedCallers[i]] = true;
}
reactor = _reactor;
swapRouter02 = _swapRouter02;
weth = WETH(payable(_swapRouter02.WETH9()));
}

/// @notice assume that we already have all output tokens
function execute(SignedOrder calldata order, bytes calldata callbackData) external onlyWhitelistedCaller {
reactor.executeWithCallback(order, callbackData);
}

/// @notice assume that we already have all output tokens
function executeBatch(SignedOrder[] calldata orders, bytes calldata callbackData) external onlyWhitelistedCaller {
reactor.executeBatchWithCallback(orders, callbackData);
}

/// @notice fill UniswapX orders using SwapRouter02
/// @param callbackData It has the below encoded:
/// address[] memory tokensToApproveForSwapRouter02: Max approve these tokens to swapRouter02
/// address[] memory tokensToApproveForReactor: Max approve these tokens to reactor
/// bytes[] memory multicallData: Pass into swapRouter02.multicall()
function reactorCallback(ResolvedOrder[] calldata, bytes calldata callbackData) external onlyReactor {
(
address[] memory tokensToApproveForSwapRouter02,
address[] memory tokensToApproveForReactor,
bytes[] memory multicallData
) = abi.decode(callbackData, (address[], address[], bytes[]));

unchecked {
for (uint256 i = 0; i < tokensToApproveForSwapRouter02.length; i++) {
ERC20(tokensToApproveForSwapRouter02[i]).safeApprove(address(swapRouter02), type(uint256).max);
}

for (uint256 i = 0; i < tokensToApproveForReactor.length; i++) {
ERC20(tokensToApproveForReactor[i]).safeApprove(address(reactor), type(uint256).max);
}
}

swapRouter02.multicall(type(uint256).max, multicallData);

// transfer any native balance to the reactor
// it will refund any excess
if (address(this).balance > 0) {
CurrencyLibrary.transferNative(address(reactor), address(this).balance);
}
}

/// @notice This function can be used to convert ERC20s to ETH that remains in this contract
/// @param tokensToApprove Max approve these tokens to swapRouter02
/// @param multicallData Pass into swapRouter02.multicall()
function multicall(ERC20[] calldata tokensToApprove, bytes[] calldata multicallData) external onlyOwner {
for (uint256 i = 0; i < tokensToApprove.length; i++) {
tokensToApprove[i].safeApprove(address(swapRouter02), type(uint256).max);
}
swapRouter02.multicall(type(uint256).max, multicallData);
}

/// @notice Unwraps the contract's WETH9 balance and sends it to the recipient as ETH. Can only be called by owner.
/// @param recipient The address receiving ETH
function unwrapWETH(address recipient) external onlyOwner {
uint256 balanceWETH = weth.balanceOf(address(this));

weth.withdraw(balanceWETH);
SafeTransferLib.safeTransferETH(recipient, address(this).balance);
}

/// @notice Transfer all ETH in this contract to the recipient. Can only be called by owner.
/// @param recipient The recipient of the ETH
function withdrawETH(address recipient) external onlyOwner {
SafeTransferLib.safeTransferETH(recipient, address(this).balance);
}

/// @notice Necessary for this contract to receive ETH when calling unwrapWETH()
receive() external payable {}
}
Loading