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

feat: add universal router executor #316

Merged
merged 6 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
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
26 changes: 26 additions & 0 deletions script/DeployUniversalRouterExecutor.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.13;

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

contract DeployUniversalRouterExecutor is Script {
function setUp() public {}

function run() public returns (UniversalRouterExecutor executor) {
uint256 privateKey = vm.envUint("FOUNDRY_PRIVATE_KEY");
IReactor reactor = IReactor(vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_REACTOR"));
address whitelistedCaller = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_WHITELISTED_CALLER");
address owner = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_OWNER");
address universalRouter = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_UNIVERSALROUTER");

vm.startBroadcast(privateKey);
executor = new UniversalRouterExecutor{salt: 0x00}(whitelistedCaller, reactor, owner, universalRouter);
vm.stopBroadcast();

console2.log("UniversalRouterExecutor", address(executor));
console2.log("owner", executor.owner());
}
}
103 changes: 103 additions & 0 deletions src/sample-executors/UniversalRouterExecutor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// 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";

/// @notice A fill contract that uses UniversalRouter to execute trades
contract UniversalRouterExecutor 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();

address private immutable universalRouter;
address private immutable whitelistedCaller;
IReactor private immutable reactor;

modifier onlyWhitelistedCaller() {
if (msg.sender != whitelistedCaller) {
revert CallerNotWhitelisted();
}
_;
}

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

constructor(address _whitelistedCaller, IReactor _reactor, address _owner, address _universalRouter)
Owned(_owner)
{
whitelistedCaller = _whitelistedCaller;
reactor = _reactor;
universalRouter = _universalRouter;
}

/// @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 UniversalRouter
/// @param callbackData It has the below encoded:
/// address[] memory tokensToApproveForUniversalRouter: Max approve these tokens to universalRouter
/// address[] memory tokensToApproveForReactor: Max approve these tokens to reactor
/// bytes[] memory data: execution data
function reactorCallback(ResolvedOrder[] calldata, bytes calldata callbackData) external onlyReactor {
(
address[] memory tokensToApproveForUniversalRouter,
address[] memory tokensToApproveForReactor,
bytes memory data
) = abi.decode(callbackData, (address[], address[], bytes));

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

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

(bool success, bytes memory returnData) = universalRouter.call(data);
if (!success) {
assembly {
revert(add(returnData, 32), mload(returnData))
}
}

// 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 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