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
Show file tree
Hide file tree
Changes from all commits
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
29 changes: 29 additions & 0 deletions script/DeployMultiFillerExecutor.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.13;

import "forge-std/console2.sol";
import "forge-std/Script.sol";
import {MultiFillerSwapRouter02Executor} from "../src/sample-executors/MultiFillerSwapRouter02Executor.sol";
import {ISwapRouter02} from "../src/external/ISwapRouter02.sol";
import {IReactor} from "../src/interfaces/IReactor.sol";

contract DeploySwapRouter02Executor is Script {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cahnge name

function setUp() public {}

function run() public returns (MultiFillerSwapRouter02Executor executor) {
uint256 privateKey = vm.envUint("FOUNDRY_PRIVATE_KEY");
IReactor reactor = IReactor(vm.envAddress("FOUNDRY_SWAPROUTER02EXECUTOR_DEPLOY_REACTOR"));
bytes memory encodedAddresses = vm.envBytes("FOUNDRY_SWAPROUTER02EXECUTOR_DEPLOY_WHITELISTED_CALLER");
address owner = vm.envAddress("FOUNDRY_SWAPROUTER02EXECUTOR_DEPLOY_OWNER");
ISwapRouter02 swapRouter02 = ISwapRouter02(vm.envAddress("FOUNDRY_SWAPROUTER02EXECUTOR_DEPLOY_SWAPROUTER02"));

address[] memory decodedAddresses = abi.decode(encodedAddresses, (address[]));

vm.startBroadcast(privateKey);
executor = new MultiFillerSwapRouter02Executor{salt: 0x00}(decodedAddresses, reactor, owner, swapRouter02);
vm.stopBroadcast();

console2.log("SwapRouter02Executor", address(executor));
console2.log("owner", executor.owner());
}
}
138 changes: 138 additions & 0 deletions src/sample-executors/MultiFillerSwapRouter02Executor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// 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 MultiFillerSwapRouter02Executor is IReactorCallback, Owned {
using SafeTransferLib for ERC20;
using CurrencyLibrary for address;

event ReactorChanged(address newReactor, address oldReactor);

/// @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 public 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 Transfer the entire balance of an ERC20 token in this contract to a recipient. Can only be called by owner.
/// @param token The ERC20 token to withdraw
/// @param to The recipient of the tokens
function withdrawERC20(ERC20 token, address to) external onlyOwner {
token.safeTransfer(to, token.balanceOf(address(this)));
}

/// @notice Update the reactor contract address. Can only be called by owner.
/// @param _reactor The new reactor contract address
function updateReactor(IReactor _reactor) external onlyOwner {
emit ReactorChanged(address(_reactor), address(reactor));
reactor = _reactor;
}

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