This repository has been archived by the owner on Jul 2, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathMultiInvokerRollup.sol
344 lines (278 loc) · 13.6 KB
/
MultiInvokerRollup.sol
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.17;
import "./MultiInvoker.sol";
import "../interfaces/IMultiInvokerRollup.sol";
/**
* @title MultiInvokerRollup
* @notice A calldata-optimized implementation of the Perennial MultiInvoker
* @dev Supports the following encoding algorithm:
1) List of Actions
* At the top-level input, the MultiInvoker takes a list of actions
* Each action is encoded using the below specification then concatenated together
2) Actions
* First byte is the uint8 of the action's enum
* Rest of the data is a list of params encoded using the below specification then concatenated together
3) Parameters
* uint256 - first byte is length, data is packed into smallest byte length that will fit value
* uint256[] - first byte is array length, each element is packed as a uint256 and concatenated together
* Address
1) If address is already cached, index of the address is encoded as a uint256
2) Otherwise the first byte is encoded as 0, and the following 20 bytes are the address
*/
contract MultiInvokerRollup is IMultiInvokerRollup, MultiInvoker {
/// @dev Number of bytes in a uint256 type
uint256 private constant UINT256_LENGTH = 32;
/// @dev Number of bytes in a address type
uint256 private constant ADDRESS_LENGTH = 20;
/// @dev Number of bytes in a uint8 type
uint256 private constant UINT8_LENGTH = 1;
/// @dev Array of all stored addresses (users, products, vaults, etc) for calldata packing
address[] public addressCache;
/// @dev Index lookup of above array for constructing calldata
mapping(address => uint256) public addressLookup;
/// @dev magic byte to prepend to calldata for the fallback.
/// Prevents public fns from being called by arbitrary fallback data
uint8 public constant INVOKE_ID = 73;
/**
* @notice Constructs the contract
* @param usdc_ The USDC token contract address
* @param reserve_ The DSU batcher contract address
* @param reserve_ The DSU reserve contract address
* @param controller_ The Perennial controller contract address
*/
constructor(Token6 usdc_, IBatcher batcher_, IEmptySetReserve reserve_, IController controller_)
MultiInvoker(usdc_, batcher_, reserve_, controller_)
{
_cacheAddress(address(0)); // Cache 0-address to avoid 0-index lookup collision
}
/**
* @notice This function serves exactly the same as invoke(Invocation[] memory invocations),
* but includes logic to handle the highly packed calldata
* @dev Fallback eliminates need for 4 byte sig. MUST prepend INVOKE_ID to calldata
* @param input Packed data to pass to invoke logic
* @return required no-op
*/
fallback (bytes calldata input) external returns (bytes memory) {
PTR memory ptr;
if (_readUint8(input, ptr) != INVOKE_ID) revert MultiInvokerRollupMissingMagicByteError();
_decodeFallbackAndInvoke(input, ptr);
return "";
}
/**
* @notice Processes invocation with highly packed data
* @dev
* Encoding Scheme:
* [0:1] => uint action
* [1:2] => uint length of current encoded type
* [2:length] => current encoded type (see individual type decoding functions)
* @param input Packed data to pass to invoke logic
*/
function _decodeFallbackAndInvoke(bytes calldata input, PTR memory ptr) internal {
while (ptr.pos < input.length) {
PerennialAction action = PerennialAction(_readUint8(input, ptr));
if (action == PerennialAction.DEPOSIT) {
address account = _readAndCacheAddress(input, ptr);
address product = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_deposit(account, IProduct(product), amount);
} else if (action == PerennialAction.WITHDRAW) {
address receiver = _readAndCacheAddress(input, ptr);
address product = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_withdraw(receiver, IProduct(product), amount);
} else if (action == PerennialAction.OPEN_TAKE) {
address product = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_openTake(IProduct(product), amount);
} else if (action == PerennialAction.CLOSE_TAKE) {
address product = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_closeTake(IProduct(product), amount);
} else if (action == PerennialAction.OPEN_MAKE) {
address product = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_openMake(IProduct(product), amount);
} else if (action == PerennialAction.CLOSE_MAKE) {
address product = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_closeMake(IProduct(product), amount);
} else if (action == PerennialAction.CLAIM) {
address product = _readAndCacheAddress(input, ptr);
uint256[] memory programIds = _readUint256Array(input, ptr);
_claim(IProduct(product), programIds);
} else if (action == PerennialAction.WRAP) {
address receiver = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_wrap(receiver, amount);
} else if (action == PerennialAction.UNWRAP) {
address receiver = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_unwrap(receiver, amount);
} else if (action == PerennialAction.WRAP_AND_DEPOSIT) {
address account = _readAndCacheAddress(input, ptr);
address product = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_wrapAndDeposit(account, IProduct(product), amount);
} else if (action == PerennialAction.WITHDRAW_AND_UNWRAP) {
address receiver = _readAndCacheAddress(input, ptr);
address product = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_withdrawAndUnwrap(receiver, IProduct(product), amount);
} else if (action == PerennialAction.VAULT_DEPOSIT) {
address depositer = _readAndCacheAddress(input, ptr);
address vault = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_vaultDeposit(depositer, IPerennialVault(vault), amount);
} else if (action == PerennialAction.VAULT_REDEEM) {
address vault = _readAndCacheAddress(input, ptr);
UFixed18 shares = _readUFixed18(input, ptr);
_vaultRedeem(IPerennialVault(vault), shares);
} else if (action == PerennialAction.VAULT_CLAIM) {
address owner = _readAndCacheAddress(input, ptr);
address vault = _readAndCacheAddress(input, ptr);
_vaultClaim(IPerennialVault(vault), owner);
} else if (action == PerennialAction.VAULT_WRAP_AND_DEPOSIT) {
address account = _readAndCacheAddress(input, ptr);
address vault = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
_vaultWrapAndDeposit(account, IPerennialVault(vault), amount);
} else if (action == PerennialAction.CHARGE_FEE) {
address receiver = _readAndCacheAddress(input, ptr);
UFixed18 amount = _readUFixed18(input, ptr);
bool wrapped = _readBool(input, ptr);
_chargeFee(receiver, amount, wrapped);
}
}
}
/**
* @notice Unchecked sets address in cache
* @param value Address to add to cache
*/
function _cacheAddress(address value) private {
uint256 index = addressCache.length;
addressCache.push(value);
addressLookup[value] = index;
emit AddressAddedToCache(value, index);
}
/**
* @notice Helper function to get address from calldata
* @param input Full calldata payload
* @param ptr Current index of input to start decoding
* @return result The decoded address
*/
function _readAndCacheAddress(bytes calldata input, PTR memory ptr) private returns (address result) {
uint8 len = _readUint8(input, ptr);
// user is new to registry, add next 20 bytes as address to registry and return address
if (len == 0) {
result = _bytesToAddress(input[ptr.pos:ptr.pos + ADDRESS_LENGTH]);
ptr.pos += ADDRESS_LENGTH;
_cacheAddress(result);
} else {
uint256 idx = _bytesToUint256(input, ptr.pos, len);
ptr.pos += len;
result = _lookupAddress(idx);
}
}
/**
* @notice Checked gets the address in cache mapped to the cache index
* @dev There is an issue with the calldata if a txn uses cache before caching address
* @param index The cache index
* @return result Address stored at cache index
*/
function _lookupAddress(uint256 index) private view returns (address result) {
result = addressCache[index];
if (result == address(0)) revert MultiInvokerRollupAddressIndexOutOfBoundsError();
}
/**
* @notice Helper function to get bool from calldata
* @param input Full calldata payload
* @param ptr Current index of input to start decoding
* @return result The decoded bool
*/
function _readBool(bytes calldata input, PTR memory ptr) private pure returns (bool result) {
uint8 dir = _readUint8(input, ptr);
result = dir > 0;
}
/**
* @notice Wraps next length of bytes as UFixed18
* @param input Full calldata payload
* @param ptr Current index of input to start decoding
* @return result The decoded UFixed18
*/
function _readUFixed18(bytes calldata input, PTR memory ptr) private pure returns (UFixed18 result) {
return UFixed18.wrap(_readUint256(input, ptr));
}
/**
* @notice Unpacks next length of bytes as lengths of bytes into array of uint256
* @param input Full calldata payload
* @param ptr Current index of input to start decoding
* @return result ProgramIds for CLAIM action
*/
function _readUint256Array(bytes calldata input, PTR memory ptr) private pure returns (uint256[] memory result) {
uint8 arrayLen = _readUint8(input, ptr);
result = new uint256[](arrayLen);
for (uint256 i; i < arrayLen; i++) {
result[i] = _readUint256(input, ptr);
}
}
/**
* @notice Helper function to get uint8 length from calldata
* @param input Full calldata payload
* @param ptr Current index of input to start decoding
* @return result The decoded uint8 length
*/
function _readUint8(bytes calldata input, PTR memory ptr) private pure returns (uint8 result) {
result = _bytesToUint8(input, ptr.pos);
ptr.pos += UINT8_LENGTH;
}
/**
* @notice Helper function to get uint256 from calldata
* @param input Full calldata payload
* @param ptr Current index of input to start decoding
* @return result The decoded uint256
*/
function _readUint256(bytes calldata input, PTR memory ptr) private pure returns (uint256 result) {
uint8 len = _readUint8(input, ptr);
if (len > UINT256_LENGTH) revert MultiInvokerRollupInvalidUint256LengthError();
result = _bytesToUint256(input, ptr.pos, len);
ptr.pos += len;
}
/**
* @notice Implementation of GNSPS' standard BytesLib.sol
* @param input 1 byte slice to convert to uint8 to decode lengths
* @return result The uint8 representation of input
*/
function _bytesToUint8(bytes calldata input, uint256 pos) private pure returns (uint8 result) {
assembly {
// 1) load calldata into temp starting at ptr position
let temp := calldataload(add(input.offset, pos))
// 2) shifts the calldata such that only the first byte is stored in result
result := shr(mul(8, sub(UINT256_LENGTH, UINT8_LENGTH)), temp)
}
}
/**
* @dev This is called in decodeAccount and decodeProduct which both only pass 20 byte slices
* @notice Unchecked force of 20 bytes into address
* @param input The 20 bytes to be converted to address
* @return result Address representation of `input`
*/
function _bytesToAddress(bytes memory input) private pure returns (address result) {
assembly {
result := mload(add(input, ADDRESS_LENGTH))
}
}
/**
* @notice Unchecked loads arbitrarily-sized bytes into a uint
* @dev Bytes length enforced as < max word size
* @param input The bytes to convert to uint256
* @return result The resulting uint256
*/
function _bytesToUint256(bytes calldata input, uint256 pos, uint256 len) private pure returns (uint256 result) {
assembly {
// 1) load the calldata into result starting at the ptr position
result := calldataload(add(input.offset, pos))
// 2) shifts the calldata such that only the next length of bytes specified by `len` populates the uint256 result
result := shr(mul(8, sub(UINT256_LENGTH, len)), result)
}
}
}