Skip to content

Commit 5285ce6

Browse files
committed
add universal router executor
1 parent 2954f97 commit 5285ce6

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity ^0.8.13;
3+
4+
import "forge-std/console2.sol";
5+
import "forge-std/Script.sol";
6+
import {UniversalRouterExecutor} from "../src/sample-executors/UniversalRouterExecutor.sol";
7+
import {IReactor} from "../src/interfaces/IReactor.sol";
8+
9+
contract DeployUniversalRouterExecutor is Script {
10+
function setUp() public {}
11+
12+
function run() public returns (UniversalRouterExecutor executor) {
13+
uint256 privateKey = vm.envUint("FOUNDRY_PRIVATE_KEY");
14+
IReactor reactor = IReactor(vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_REACTOR"));
15+
address whitelistedCaller = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_WHITELISTED_CALLER");
16+
address owner = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_OWNER");
17+
address universalRouter = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_UNIVERSALROUTER");
18+
19+
vm.startBroadcast(privateKey);
20+
executor = new UniversalRouterExecutor{salt: 0x00}(whitelistedCaller, reactor, owner, universalRouter);
21+
vm.stopBroadcast();
22+
23+
console2.log("UniversalRouterExecutor", address(executor));
24+
console2.log("owner", executor.owner());
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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

Comments
 (0)