Skip to content

Commit

Permalink
example multifiller executor (#272)
Browse files Browse the repository at this point in the history
* multifiller executor

* add reactorChanged

* make reactor public

* add deploy script

* forge fmt

* fix names

* add addr encoding comment
  • Loading branch information
ConjunctiveNormalForm authored Dec 18, 2024
1 parent 9ba6ffd commit 2954f97
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 0 deletions.
30 changes: 30 additions & 0 deletions script/DeployMultiFillerExecutor.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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 DeployMultiFillerExecutor is Script {
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"));
// can encode with cast abi-encode "foo(address[])" "[addr1, addr2, ...]"
bytes memory encodedAddresses = vm.envBytes("FOUNDRY_MULTIFILLER_ADDRESSES_ENCODED");
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 {}
}

0 comments on commit 2954f97

Please sign in to comment.