-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathReactor.sol
More file actions
240 lines (209 loc) · 9.06 KB
/
Copy pathReactor.sol
File metadata and controls
240 lines (209 loc) · 9.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2026 Symbiotic
pragma solidity 0.8.28;
import {IExecutor} from "./interfaces/IExecutor.sol";
import {IReactor, NATIVE, ORDER_TYPEHASH, OUTPUT_TYPEHASH, REQUEST_TYPEHASH} from "./interfaces/IReactor.sol";
import {IRegistry} from "./interfaces/IRegistry.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
/// @title Reactor
/// @notice Contract for approved RWA intake, redemption-account routing, and executor invocation.
contract Reactor is EIP712, IReactor, ReentrancyGuardTransient {
using Address for address payable;
using BitMaps for BitMaps.BitMap;
using SafeERC20 for IERC20;
/* IMMUTABLES */
/// @inheritdoc IReactor
address public immutable LIQUID_LANE_ADAPTER_FACTORY;
/* STORAGE */
/// @dev Tracks consumed request nonces by swapper.
mapping(address swapper => BitMaps.BitMap nonces) internal _isUsedNonce;
/* CONSTRUCTOR */
constructor(address liquidLaneAdapterFactory) EIP712("Reactor", "1") {
LIQUID_LANE_ADAPTER_FACTORY = liquidLaneAdapterFactory;
}
/* PUBLIC FUNCTIONS */
/// @notice Returns whether a swapper request nonce has been consumed.
/// @param swapper Address that owns the nonce.
/// @param nonce Request nonce to check.
/// @return used Whether the nonce has already been consumed.
function isUsedNonce(address swapper, uint256 nonce) public view returns (bool used) {
return _isUsedNonce[swapper].get(nonce);
}
/// @inheritdoc IReactor
function invalidateNonce(uint256 nonce) public {
_isUsedNonce[msg.sender].set(nonce);
emit InvalidateNonce(msg.sender, nonce);
}
/// @inheritdoc IReactor
function fill(
Order memory order,
bytes memory protocolSignature,
SwapInput memory swapInput,
bytes memory executorData
) public {
SwapInput[] memory swapInputs = new SwapInput[](1);
swapInputs[0] = swapInput;
_fill(order, protocolSignature, swapInputs, new DiscountSwapInput[](0), executorData);
}
/// @inheritdoc IReactor
function fill(
Order memory order,
bytes memory protocolSignature,
SwapInput[] memory swapInputs,
bytes memory executorData
) public {
_fill(order, protocolSignature, swapInputs, new DiscountSwapInput[](0), executorData);
}
/// @inheritdoc IReactor
function fill(
Order memory order,
bytes memory protocolSignature,
SwapInput[] memory swapInputs,
DiscountSwapInput[] memory discountSwapInputs,
bytes memory executorData
) public {
_fill(order, protocolSignature, swapInputs, discountSwapInputs, executorData);
}
/* INTERNAL FUNCTIONS */
/// @dev Validates and executes a fill across one or more adapter swap legs.
/// @param order The signed protocol order.
/// @param protocolSignature The protocol signature over `order`.
/// @param swapInputs The direct adapter swaps that consume the input.
/// @param discountSwapInputs The discount-backed adapter swaps that consume the input.
/// @param executorData The opaque executor payload forwarded to the executor.
function _fill(
Order memory order,
bytes memory protocolSignature,
SwapInput[] memory swapInputs,
DiscountSwapInput[] memory discountSwapInputs,
bytes memory executorData
) internal nonReentrant {
if (order.filler != msg.sender) {
revert InvalidFiller();
}
if (block.timestamp > order.request.deadline) {
revert ExpiredRequest();
}
if (isUsedNonce(order.swapper, order.request.nonce)) {
revert NonceUsed();
}
if (!SignatureChecker.isValidSignatureNow(
order.request.protocol, _hashTypedDataV4(_hashOrder(order)), protocolSignature
)) {
revert InvalidProtocolSignature();
}
if (!SignatureChecker.isValidSignatureNow(
order.swapper, _hashTypedDataV4(_hashRequest(order.request)), order.swapperSignature
)) {
revert InvalidProtocolSignature();
}
uint256 totalAmountIn;
for (uint256 i; i < swapInputs.length; ++i) {
if (!IRegistry(LIQUID_LANE_ADAPTER_FACTORY).isEntity(swapInputs[i].adapter)) {
revert InvalidAdapter();
}
if (swapInputs[i].swap.tokenIn != order.request.tokenIn) {
revert InvalidTokenIn();
}
totalAmountIn += swapInputs[i].swap.amountIn;
}
for (uint256 i; i < discountSwapInputs.length; ++i) {
if (!IRegistry(LIQUID_LANE_ADAPTER_FACTORY).isEntity(discountSwapInputs[i].adapter)) {
revert InvalidAdapter();
}
if (discountSwapInputs[i].discountSwap.discount.tokenToRedeem != order.request.tokenIn) {
revert InvalidTokenIn();
}
totalAmountIn += discountSwapInputs[i].amountIn;
}
if (totalAmountIn != order.request.amountIn) {
revert InvalidAmountIn();
}
if (order.request.outputs.length != order.outputs.length) {
revert InvalidOutput();
}
for (uint256 i; i < order.request.outputs.length; ++i) {
Output memory requestOutput = order.request.outputs[i];
Output memory orderOutput = order.outputs[i];
if (
orderOutput.token != requestOutput.token || orderOutput.recipient != requestOutput.recipient
|| orderOutput.amount < requestOutput.amount
) {
revert InvalidOutput();
}
}
_isUsedNonce[order.swapper].set(order.request.nonce);
for (uint256 i; i < swapInputs.length; ++i) {
IERC20(order.request.tokenIn)
.safeTransferFrom(order.swapper, swapInputs[i].adapter, swapInputs[i].swap.amountIn);
}
for (uint256 i; i < discountSwapInputs.length; ++i) {
IERC20(order.request.tokenIn)
.safeTransferFrom(order.swapper, discountSwapInputs[i].adapter, discountSwapInputs[i].amountIn);
}
IExecutor(msg.sender).execute(order, swapInputs, discountSwapInputs, executorData);
for (uint256 i; i < order.outputs.length; ++i) {
if (order.outputs[i].token == NATIVE) {
payable(order.outputs[i].recipient).sendValue(order.outputs[i].amount);
} else {
IERC20(order.outputs[i].token)
.safeTransferFrom(msg.sender, order.outputs[i].recipient, order.outputs[i].amount);
}
}
uint256 balance = address(this).balance;
if (balance > 0) {
payable(msg.sender).sendValue(balance);
}
emit Fill(order);
}
/// @dev Hashes an order according to the Reactor EIP-712 schema.
/// @param order The order to hash.
/// @return orderHash The EIP-712 struct hash for the order.
function _hashOrder(Order memory order) internal pure returns (bytes32) {
return keccak256(
abi.encode(
ORDER_TYPEHASH,
_hashRequest(order.request),
keccak256(order.swapperSignature),
order.swapper,
order.filler,
_hashOutputs(order.outputs)
)
);
}
/// @dev Hashes a request according to the Reactor EIP-712 schema.
/// @param request The request to hash.
/// @return requestHash The EIP-712 struct hash for the request.
function _hashRequest(Request memory request) internal pure returns (bytes32) {
return keccak256(
abi.encode(
REQUEST_TYPEHASH,
request.tokenIn,
request.amountIn,
_hashOutputs(request.outputs),
request.deadline,
request.nonce,
request.protocol
)
);
}
/// @dev Hashes output arrays according to the Reactor EIP-712 schema.
/// @param outputs The outputs to hash.
/// @return The packed hash of individual output hashes.
function _hashOutputs(Output[] memory outputs) internal pure returns (bytes32) {
bytes32[] memory outputHashes = new bytes32[](outputs.length);
for (uint256 i; i < outputs.length; ++i) {
outputHashes[i] = keccak256(abi.encode(OUTPUT_TYPEHASH, outputs[i]));
}
return keccak256(abi.encodePacked(outputHashes));
}
/* RECEIVE FUNCTION */
/// @dev Accepts native asset refunds from the executor.
receive() external payable {}
}