-
Notifications
You must be signed in to change notification settings - Fork 0
/
UniversalRouterCalldataAnalyze
435 lines (319 loc) · 20.2 KB
/
UniversalRouterCalldataAnalyze
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
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
https://etherscan.io/tx/0x02e081d8014c0a06d0a7a3ce49bfe98cc7e89f8480abb315a807fcc06003a6c3
calldata RAW
0x3593564c
0000000000000000000000000000000000000000000000000000000000000060
00000000000000000000000000000000000000000000000000000000000000a0
00000000000000000000000000000000000000000000000000000000651a8a6f
0000000000000000000000000000000000000000000000000000000000000004
0a000c0400000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000004
0000000000000000000000000000000000000000000000000000000000000080
0000000000000000000000000000000000000000000000000000000000000200
0000000000000000000000000000000000000000000000000000000000000320
0000000000000000000000000000000000000000000000000000000000000380
0000000000000000000000000000000000000000000000000000000000000160
00000000000000000000000004fb784d1be2b8e7079d34df3addd8bfcc2f0ccf
000000000000000000000000000000000000000000004c88031c70f8329886ff
000000000000000000000000000000000000000000000000000000006542105d
0000000000000000000000000000000000000000000000000000000000000009
000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b
000000000000000000000000000000000000000000000000000000006542105d
00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000041
284b9995f4ea5f891ffc83c2de04c6df5c4e1f1d0a058f6ac19854116a81df9c
2a3cc1d05e13557f6ed8ae4856aeb9094c904114c64476ea905f9a04fe00af6a
1c00000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000100
0000000000000000000000000000000000000000000000000000000000000002
000000000000000000000000000000000000000000004c88031c70f8329886ff
00000000000000000000000000000000000000000000000001199f97241d1541
00000000000000000000000000000000000000000000000000000000000000a0
0000000000000000000000000000000000000000000000000000000000000001
000000000000000000000000000000000000000000000000000000000000002b
04fb784d1be2b8e7079d34df3addd8bfcc2f0ccf002710c02aaa39b223fe8d0a
0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000060
00000000000000000000000004fb784d1be2b8e7079d34df3addd8bfcc2f0ccf
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000000
0 commands bytes 0x0a000c04
1 inputs bytes[]
0x00000000000000000000000004fb784d1be2b8e7079d34df3addd8bfcc2f0ccf000000000000000000000000000000000000000000004c88031c70f8329886ff000000000000000000000000000000000000000000000000000000006542105d0000000000000000000000000000000000000000000000000000000000000009000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b000000000000000000000000000000000000000000000000000000006542105d00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000041284b9995f4ea5f891ffc83c2de04c6df5c4e1f1d0a058f6ac19854116a81df9c2a3cc1d05e13557f6ed8ae4856aeb9094c904114c64476ea905f9a04fe00af6a1c00000000000000000000000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000004c88031c70f8329886ff00000000000000000000000000000000000000000000000001199f97241d154100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002b04fb784d1be2b8e7079d34df3addd8bfcc2f0ccf002710c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000
0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000
0x00000000000000000000000004fb784d1be2b8e7079d34df3addd8bfcc2f0ccf00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000
2 deadline uint256 1696238191
MethodID: 0x3593564c // function "execute" selector
[0]: 0000000000000000000000000000000000000000000000000000000000000060 / 96 in dec pointer for deadline (3*bytes32)
[1]: 00000000000000000000000000000000000000000000000000000000000000a0 // 160 in dec - (5*bytes32) pointer for commands
[2]: 00000000000000000000000000000000000000000000000000000000651a8a6f // 651A8A6F - deadline 1696238191
[3]: 0000000000000000000000000000000000000000000000000000000000000004 // commands.length
[4]: 0a000c0400000000000000000000000000000000000000000000000000000000 // commands 0x0a000c04 // PERMIT2_PERMIT = 0x0a // V3_SWAP_EXACT_IN = 0x00// UNWRAP_WETH = 0x0c // SWEEP = 0x04
[5]: 0000000000000000000000000000000000000000000000000000000000000004 // inputs.length (parametrs for commands)
[6]: 0000000000000000000000000000000000000000000000000000000000000080 // 128 in dec (4*32bytes) - offset for 1st input from inputs.length
[7]: 0000000000000000000000000000000000000000000000000000000000000200 // 512 in dec (16*32bytes) - offset for 2nd input from inputs.length
[8]: 0000000000000000000000000000000000000000000000000000000000000320 // 800 in dec (25*32bytes) - offset for 3rd input from inputs.length
[9]: 0000000000000000000000000000000000000000000000000000000000000380 // 896 in dec (28*32bytes) - offset for 4th input from inputs.length
// PERMIT2_PERMIT = 0x0a
[10]: 0000000000000000000000000000000000000000000000000000000000000160 //1st input.length 352 in dec (11*32bytes)
[11]: 00000000000000000000000004fb784d1be2b8e7079d34df3addd8bfcc2f0ccf //1st input // address token Mullet
[12]: 000000000000000000000000000000000000000000004c88031c70f8329886ff //1st input // in dec 361408834070410299999999 amount of tokens
[13]: 000000000000000000000000000000000000000000000000000000006542105d //1st input // expiration/deadline in dec 1698828381
[14]: 0000000000000000000000000000000000000000000000000000000000000009 //1st input // nonce 9
[15]: 000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b //1st input // spender; /universal router address
[16]: 000000000000000000000000000000000000000000000000000000006542105d //1st input // sigDeadline; in dec 1698828381
[17]: 00000000000000000000000000000000000000000000000000000000000000e0 //1st input // 224 (7*32bytes) constant
[18]: 0000000000000000000000000000000000000000000000000000000000000041 //1st input // 65 signature.length 65bytes
[19]: 284b9995f4ea5f891ffc83c2de04c6df5c4e1f1d0a058f6ac19854116a81df9c //1st input // r||s
[20]: 2a3cc1d05e13557f6ed8ae4856aeb9094c904114c64476ea905f9a04fe00af6a //1st input // r||s
[21]: 1c00000000000000000000000000000000000000000000000000000000000000 //1st input // v - 28
//V3_SWAP_EXACT_IN = 0x00
[22]: 0000000000000000000000000000000000000000000000000000000000000100 //2nd input.length 256 in dec (8*32bytes)
[23]: 0000000000000000000000000000000000000000000000000000000000000002 //2nd input // recipient (address this) / universal router
[24]: 000000000000000000000000000000000000000000004c88031c70f8329886ff //2nd input // how many tokens swap 361,408.834070410299999999 Mullet Money...(MULLET...) sell (amount in)
[25]: 00000000000000000000000000000000000000000000000001199f97241d1541 //2nd input // amountOutMin; in dec 79269939950458177 0.079269939950458177ETH
[26]: 00000000000000000000000000000000000000000000000000000000000000a0 //2nd input // in dec 160 (5*32bytes) offset from 2nd input.length to path[] or pointer where I can find path[]
[27]: 0000000000000000000000000000000000000000000000000000000000000001 //2nd input // bool payerIsUser (true)
[28]: 000000000000000000000000000000000000000000000000000000000000002b //2nd input // in dec 43 length of path[]
[29]: 04fb784d1be2b8e7079d34df3addd8bfcc2f0ccf002710c02aaa39b223fe8d0a //2nd input // 04fb784d1be2b8e7079d34df3addd8bfcc2f0ccf - address Mullet token(what we sell)
[30]: 0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000 //2nd input // c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 - WETH address 002710 ??? V3_FEE
//UNWRAP_WETH = 0x0c
[31]: 0000000000000000000000000000000000000000000000000000000000000040 //3rd input.length
[32]: 0000000000000000000000000000000000000000000000000000000000000001 //3rd input // recipient 1 = msg.sender
[33]: 0000000000000000000000000000000000000000000000000000000000000000 //3rd input // amountMin 0
//SWEEP = 0x04
[34]: 0000000000000000000000000000000000000000000000000000000000000060 //4th input.length
[35]: 00000000000000000000000004fb784d1be2b8e7079d34df3addd8bfcc2f0ccf //4th input // token
[36]: 0000000000000000000000000000000000000000000000000000000000000001 //4th input // recipent 1 = msg.sender
[37]: 0000000000000000000000000000000000000000000000000000000000000000 //4th input // amount min 0
1ST ITERATION 1ST ITERATION 1ST ITERATION 1ST ITERATION 1ST ITERATION 1ST ITERATION 1ST ITERATION 1ST ITERATION 1ST ITERATION 1ST ITERATION 1ST ITERATION 1ST ITERATION 1ST ITERATION 1ST ITERATION
function dispatch(bytes1 commandType, bytes calldata inputs) internal returns (bool success, bytes memory output) {
if (command == Commands.PERMIT2_PERMIT) {
IAllowanceTransfer.PermitSingle calldata permitSingle;
assembly {
permitSingle := inputs.offset
}
bytes calldata data = inputs.toBytes(6); // PermitSingle takes first 6 slots (0..5)
PERMIT2.permit(lockedBy, permitSingle, data);
}
function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external {
if (block.timestamp > permitSingle.sigDeadline) revert SignatureExpired(permitSingle.sigDeadline);
// Verify the signer address from the signature.
signature.verify(_hashTypedData(permitSingle.hash()), owner);
_updateApproval(permitSingle.details, owner, permitSingle.spender);
}
struct PermitDetails {
address token;
uint160 amount;
uint48 expiration;
uint48 nonce;
}
struct PermitSingle {
PermitDetails details;
address spender;
uint256 sigDeadline;
}
struct PermitSingle {
address token;
uint160 amount;
uint48 expiration;
uint48 nonce;
address spender;
uint256 sigDeadline;
}
2ND ITERATION 2ND ITERATION 2ND ITERATION 2ND ITERATION 2ND ITERATION 2ND ITERATION 2ND ITERATION 2ND ITERATION 2ND ITERATION 2ND ITERATION 2ND ITERATION 2ND ITERATION 2ND ITERATION 2ND ITERATION
function dispatch(bytes1 commandType, bytes calldata inputs) internal returns (bool success, bytes memory output) {
if (command == Commands.V3_SWAP_EXACT_IN) {
address recipient;
uint256 amountIn;
uint256 amountOutMin;
bool payerIsUser;
assembly {
recipient := calldataload(inputs.offset)
amountIn := calldataload(add(inputs.offset, 0x20))
amountOutMin := calldataload(add(inputs.offset, 0x40))
payerIsUser := calldataload(add(inputs.offset, 0x80))
}
bytes calldata path = inputs.toBytes(3);
address payer = payerIsUser ? lockedBy : address(this);
v3SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer);
}
function v3SwapExactInput(address recipient, uint256 amountIn, uint256 amountOutMinimum, bytes calldata path, address payer) internal {
if (amountIn == Constants.CONTRACT_BALANCE) { // if amountIn == 57896044618658097711785492504343953926634992332820282019728792003956564819968
address tokenIn = path.decodeFirstToken();
amountIn = ERC20(tokenIn).balanceOf(address(this));
}
uint256 amountOut;
while (true) {
bool hasMultiplePools = path.hasMultiplePools();
(int256 amount0Delta, int256 amount1Delta, bool zeroForOne) = _swap(
amountIn.toInt256(), //
hasMultiplePools ? address(this) : recipient, // for intermediate swaps, this contract custodies
path.getFirstPool(), // only the first pool is needed
payer, // for intermediate swaps, this contract custodies
true
);
amountIn = uint256(-(zeroForOne ? amount1Delta : amount0Delta));
if (hasMultiplePools) {
payer = address(this);
path = path.skipToken();
} else {
amountOut = amountIn;
break;
}
}
if (amountOut < amountOutMinimum) revert V3TooLittleReceived();
}
function hasMultiplePools(bytes calldata path) internal pure returns (bool) {
return path.length >= Constants.MULTIPLE_V3_POOLS_MIN_LENGTH; 43>=66
}
MULTIPLE_V3_POOLS_MIN_LENGTH = 20 + 3 + 20 + 20 + 3= 66
uint256 internal constant MULTIPLE_V3_POOLS_MIN_LENGTH = V3_POP_OFFSET + NEXT_V3_POOL_OFFSET;
uint256 internal constant V3_POP_OFFSET = NEXT_V3_POOL_OFFSET + ADDR_SIZE;
uint256 internal constant NEXT_V3_POOL_OFFSET = ADDR_SIZE + V3_FEE_SIZE;
uint256 internal constant ADDR_SIZE = 20;
uint256 internal constant V3_FEE_SIZE = 3;
function map(address recipient) internal view returns (address) {
if (recipient == Constants.MSG_SENDER) { //address(1);
return lockedBy;
} else if (recipient == Constants.ADDRESS_THIS) { // ADDRESS_THIS = address(2);
return address(this);
} else {
return recipient;
}
}
address internal constant ADDRESS_THIS = address(2);
function _swap(int256 amount, address recipient, bytes calldata path, address payer, bool isExactIn)
private
returns (int256 amount0Delta, int256 amount1Delta, bool zeroForOne) {
(address tokenIn, uint24 fee, address tokenOut) = path.decodeFirstPool();
zeroForOne = isExactIn ? tokenIn < tokenOut : tokenOut < tokenIn;
(amount0Delta, amount1Delta) = IUniswapV3Pool(computePoolAddress(tokenIn, tokenOut, fee)).swap(
recipient,
zeroForOne,
amount,
(zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1), // 4295128739+1 1461446703485210103287273052203988822378723970342-1
abi.encode(path, payer)
);
}
function decodeFirstPool(bytes calldata path) internal pure returns (address, uint24, address) {
return path.toPool();
}
function decodeFirstToken(bytes calldata path) internal pure returns (address tokenA) {
tokenA = path.toAddress();
}
function toAddress(bytes calldata _bytes) internal pure returns (address _address) {
if (_bytes.length < Constants.ADDR_SIZE) revert SliceOutOfBounds();
assembly {
_address := shr(96, calldataload(_bytes.offset)) // shr(x, y) logical shift right y by x bits
}
}
function toPool(bytes calldata _bytes) internal pure returns (address token0, uint24 fee, address token1) {
if (_bytes.length < Constants.V3_POP_OFFSET) revert SliceOutOfBounds();
assembly {
let firstWord := calldataload(_bytes.offset)
token0 := shr(96, firstWord)
fee := and(shr(72, firstWord), 0xffffff)
token1 := shr(96, calldataload(add(_bytes.offset, 23)))
}
}
function skipToken(bytes calldata path) internal pure returns (bytes calldata) {
return path[Constants.NEXT_V3_POOL_OFFSET:]; // 20+3
}
function getFirstPool(bytes calldata path) internal pure returns (bytes calldata) {
return path[:Constants.V3_POP_OFFSET]; ///23 + 20; 43
}
function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable checkDeadline(deadline) {
execute(commands, inputs);
}
function execute(bytes calldata commands, bytes[] calldata inputs) public payable override isNotLocked {
bool success;
bytes memory output;
uint256 numCommands = commands.length;
if (inputs.length != numCommands) revert LengthMismatch();
for (uint256 commandIndex = 0; commandIndex < numCommands;) {
bytes1 command = commands[commandIndex];
bytes calldata input = inputs[commandIndex];
(success, output) = dispatch(command, input);
if (!success && successRequired(command)) {
revert ExecutionFailed({commandIndex: commandIndex, message: output});
}
unchecked {
commandIndex++;
}
}
}
function toBytes(bytes calldata _bytes, uint256 _arg) internal pure returns (bytes calldata res) {
(uint256 length, uint256 offset) = toLengthOffset(_bytes, _arg);
assembly {
res.length := length
res.offset := offset
}
}
function toLengthOffset(bytes calldata _bytes, uint256 _arg) internal pure returns (uint256 length, uint256 offset) {
uint256 relativeOffset;
assembly {
let lengthPtr := add(_bytes.offset, calldataload(add(_bytes.offset, shl(5, _arg))))
length := calldataload(lengthPtr)
offset := add(lengthPtr, 0x20)
relativeOffset := sub(offset, _bytes.offset)
}
if (_bytes.length < length + relativeOffset) revert SliceOutOfBounds();
}
function computePoolAddress(address tokenA, address tokenB, uint24 fee) private view returns (address pool) {
if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex'ff',
UNISWAP_V3_FACTORY,
keccak256(abi.encode(tokenA, tokenB, fee)),
UNISWAP_V3_POOL_INIT_CODE_HASH
)
)
)
)
);
}
}
3RD ITERATION 3RD ITERATION 3RD ITERATION 3RD ITERATION 3RD ITERATION 3RD ITERATION 3RD ITERATION 3RD ITERATION 3RD ITERATION 3RD ITERATION 3RD ITERATION 3RD ITERATION 3RD ITERATION 3RD ITERATION
function dispatch(bytes1 commandType, bytes calldata inputs) internal returns (bool success, bytes memory output) {
} else if (command == Commands.UNWRAP_WETH) {
address recipient;
uint256 amountMin;
assembly {
recipient := calldataload(inputs.offset)
amountMin := calldataload(add(inputs.offset, 0x20))
}
Payments.unwrapWETH9(map(recipient), amountMin);
}
4TH ITERATION 4TH ITERATION 4TH ITERATION 4TH ITERATION 4TH ITERATION 4TH ITERATION 4TH ITERATION 4TH ITERATION 4TH ITERATION 4TH ITERATION 4TH ITERATION 4TH ITERATION 4TH ITERATION
function dispatch(bytes1 commandType, bytes calldata inputs) internal returns (bool success, bytes memory output) {
} else if (command == Commands.SWEEP) {
address token;
address recipient;
uint160 amountMin;
assembly {
token := calldataload(inputs.offset)
recipient := calldataload(add(inputs.offset, 0x20))
amountMin := calldataload(add(inputs.offset, 0x40))
}
Payments.sweep(token, map(recipient), amountMin);
}
function sweep(address token, address recipient, uint256 amountMinimum) internal {
uint256 balance;
if (token == Constants.ETH) { // address internal constant ETH = address(0);
balance = address(this).balance;
if (balance < amountMinimum) revert InsufficientETH();
if (balance > 0) recipient.safeTransferETH(balance);
} else {
balance = ERC20(token).balanceOf(address(this));
if (balance < amountMinimum) revert InsufficientToken();
if (balance > 0) ERC20(token).safeTransfer(recipient, balance);
}
}