• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RigoBlock / v3-contracts / 13261352678

11 Feb 2025 10:59AM UTC coverage: 84.94% (+2.6%) from 82.368%
13261352678

Pull #622

github

web-flow
Merge f22d260e3 into 08bd3b51b
Pull Request #622: feat: automated nav

761 of 962 branches covered (79.11%)

Branch coverage included in aggregate %.

523 of 711 new or added lines in 28 files covered. (73.56%)

18 existing lines in 5 files now uncovered.

1698 of 1933 relevant lines covered (87.84%)

44.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

16.18
/contracts/protocol/extensions/adapters/AUniswapDecoder.sol
1
// SPDX-License-Identifier: Apache 2.0
2
/*
3

4
 Copyright 2024 Rigo Intl.
5

6
 Licensed under the Apache License, Version 2.0 (the "License");
7
 you may not use this file except in compliance with the License.
8
 You may obtain a copy of the License at
9

10
     http://www.apache.org/licenses/LICENSE-2.0
11

12
 Unless required by applicable law or agreed to in writing, software
13
 distributed under the License is distributed on an "AS IS" BASIS,
14
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 See the License for the specific language governing permissions and
16
 limitations under the License.
17

18
*/
19

20
// solhint-disable-next-line
21
pragma solidity 0.8.28;
22

23
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
24
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
25
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
26
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
27
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
28
import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
29
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
30
import {CalldataDecoder} from "@uniswap/v4-periphery/src/libraries/CalldataDecoder.sol";
31
import {Commands} from "@uniswap/universal-router/contracts/libraries/Commands.sol";
32
import {BytesLib} from '@uniswap/universal-router/contracts/modules/uniswap/v3/BytesLib.sol';
33
import {IAUniswapRouter} from "./interfaces/IAUniswapRouter.sol";
34

35
abstract contract AUniswapDecoder {
36
    using BytesLib for bytes;
37
    using TransientStateLibrary for IPoolManager;
38
    using CalldataDecoder for bytes;
39

40
    error InvalidCommandType(uint256 commandType);
41
    error UnsupportedAction(uint256 action);
42

43
    /// @dev Only pools that do not have access to liquidity at removal are supported
44
    error LiquidityMintHookError(address hook);
45

46
    address internal constant ZERO_ADDRESS = address(0);
47
    address private immutable _wrappedNative;
48

49
    constructor(address wrappedNative) {
50
        _wrappedNative = wrappedNative;
3✔
51
    }
52

53
    function uniV4Posm() public view virtual returns (IPositionManager);
54

55
    /// @dev Decodes the input for a command.
56
    /// @param commandType The command type to decode.
57
    /// @param inputs The encoded input data.
58
    /// @return params containing relevant outputs.
59
    function _decodeInput(
60
        bytes1 commandType,
61
        bytes calldata inputs,
62
        IAUniswapRouter.Parameters memory params
63
    ) internal returns (IAUniswapRouter.Parameters memory) {
64
        uint256 command = uint8(commandType & Commands.COMMAND_TYPE_MASK);
×
65

66
        // 0x00 <= command < 0x21
67
        if (command < Commands.EXECUTE_SUB_PLAN) {
×
68
            // 0x00 <= command < 0x10
69
            if (command < Commands.V4_SWAP) {
×
70
                // 0x00 <= command < 0x08
71
                if (command < Commands.V2_SWAP_EXACT_IN) {
×
72
                    if (command == Commands.V3_SWAP_EXACT_IN) {
×
73
                        // address recipient, uint256 amountIn, uint256 amountOutMin, bytes memory path, bool payerIsUser
NEW
74
                        (address recipient,,,,) = abi.decode(inputs, (address, uint256, uint256, bytes, bool));
×
75
                        bytes calldata path = inputs.toBytes(3);
×
NEW
76
                        params.recipients = _addUnique(params.recipients, recipient);
×
NEW
77
                        params.tokensIn = _addUnique(params.tokensIn, path.toAddress());
×
NEW
78
                        params.tokensOut = _addUnique(params.tokensOut, path.toBytes(path.length - 20).toAddress());
×
NEW
79
                        params.recipients = _addUnique(params.recipients, recipient);
×
NEW
80
                        return params;
×
81
                    } else if (command == Commands.V3_SWAP_EXACT_OUT) {
×
82
                        // address recipient, uint256 amountOut, uint256 amountInMax, bytes memory path, bool payerIsUser
NEW
83
                        (address recipient,,,,) = abi.decode(inputs, (address, uint256, uint256, bytes, bool));
×
84
                        bytes calldata path = inputs.toBytes(3);
×
NEW
85
                        params.recipients = _addUnique(params.recipients, recipient);
×
NEW
86
                        params.tokensOut = _addUnique(params.tokensOut, path.toAddress());
×
NEW
87
                        params.tokensIn = _addUnique(params.tokensIn, path.toBytes(path.length - 20).toAddress());
×
NEW
88
                        params.recipients = _addUnique(params.recipients, recipient);
×
NEW
89
                        return params;
×
90
                    } else if (command == Commands.PERMIT2_TRANSFER_FROM) {
×
NEW
91
                        revert InvalidCommandType(command);
×
92
                    } else if (command == Commands.PERMIT2_PERMIT_BATCH) {
×
NEW
93
                        revert InvalidCommandType(command);
×
94
                    } else if (command == Commands.SWEEP) {
×
95
                        // sweep is used when the router is used for transfers to clear leftover
96
                        // address token, address recipient, uint160 amountMin
NEW
97
                        (address token, address recipient, ) = abi.decode(inputs, (address, address, uint256));
×
NEW
98
                        params.tokensOut = _addUnique(params.tokensOut, token);
×
NEW
99
                        params.recipients = _addUnique(params.recipients, recipient);
×
NEW
100
                        return params;
×
101
                    } else if (command == Commands.TRANSFER) {
×
102
                        // address token, address recipient, uint256 value
NEW
103
                        (address token, address recipient,) = abi.decode(inputs, (address, address, uint256));
×
NEW
104
                        params.tokensOut = _addUnique(params.tokensOut, token);
×
NEW
105
                        params.recipients = _addUnique(params.recipients, recipient);
×
NEW
106
                        return params;
×
107
                    } else if (command == Commands.PAY_PORTION) {
×
108
                        // address token, address recipient, uint256 bips
NEW
109
                        (address token, address recipient, ) = abi.decode(inputs, (address, address, uint256));
×
NEW
110
                        params.tokensOut = _addUnique(params.tokensOut, token);
×
NEW
111
                        params.recipients = _addUnique(params.recipients, recipient);
×
NEW
112
                        return params;
×
113
                    } else {
114
                        // placeholder area for command 0x07
115
                        revert InvalidCommandType(command);
×
116
                    }
117
                } else {
118
                    // 0x08 <= command < 0x10
119
                    if (command == Commands.V2_SWAP_EXACT_IN) {
×
120
                        // address recipient, uint256 amountIn, uint256 amountOutMin, bytes memory path, bool payerIsUser
NEW
121
                        (address recipient, uint256 amountIn,,,) =
×
122
                            abi.decode(inputs, (address, uint256, uint256, bytes, bool));
NEW
123
                        params.recipients = _addUnique(params.recipients, recipient);
×
124
                        address[] calldata path = inputs.toAddressArray(3);
×
NEW
125
                        params.tokensIn = _addUnique(params.tokensIn, path[0]);
×
NEW
126
                        params.tokensOut = _addUnique(params.tokensOut, path[path.length - 1]);
×
NEW
127
                        params.recipients = _addUnique(params.recipients, recipient);
×
NEW
128
                        params.value += path[0] == ZERO_ADDRESS ? amountIn : 0;
×
NEW
129
                        return params;
×
130
                    } else if (command == Commands.V2_SWAP_EXACT_OUT) {
×
131
                        // address recipient, uint256 amountOut, uint256 amountInMax, bytes memory path, bool payerIsUser
NEW
132
                        (address recipient,, uint256 amountInMax,,) =
×
133
                            abi.decode(inputs, (address, uint256, uint256, bytes, bool));
NEW
134
                        params.recipients = _addUnique(params.recipients, recipient);
×
UNCOV
135
                        address[] calldata path = inputs.toAddressArray(3);
×
NEW
136
                        params.tokensOut = _addUnique(params.tokensOut, path[0]);
×
NEW
137
                        params.tokensIn = _addUnique(params.tokensIn, path[path.length - 1]);
×
NEW
138
                        params.recipients = _addUnique(params.recipients, recipient);
×
NEW
139
                        params.value += path[0] == ZERO_ADDRESS ? amountInMax : 0;
×
NEW
140
                        return params;
×
141
                    } else if (command == Commands.PERMIT2_PERMIT) {
×
NEW
142
                        revert InvalidCommandType(command);
×
143
                    } else if (command == Commands.WRAP_ETH) {
×
144
                        (address recipient, uint256 amount) = abi.decode(inputs, (address, uint256));
×
NEW
145
                        params.recipients = _addUnique(params.recipients, recipient);
×
NEW
146
                        params.tokensOut = _addUnique(params.tokensOut, _wrappedNative);
×
NEW
147
                        params.value += amount;
×
NEW
148
                        return params;
×
149
                    } else if (command == Commands.UNWRAP_WETH) {
×
150
                        // address recipient, uint256 amountMin
NEW
151
                        (address recipient, ) = abi.decode(inputs, (address, uint256));
×
NEW
152
                        params.tokensOut = _addUnique(params.tokensOut, ZERO_ADDRESS);
×
NEW
153
                        params.recipients = _addUnique(params.recipients, recipient);
×
NEW
154
                        return params;
×
155
                    } else if (command == Commands.PERMIT2_TRANSFER_FROM_BATCH) {
×
NEW
156
                        revert InvalidCommandType(command);
×
157
                    } else if (command == Commands.BALANCE_CHECK_ERC20) {
×
158
                        // no further assertion needed as uni router uses staticcall
159
                    } else {
160
                        // placeholder area for command 0x0f
161
                        revert InvalidCommandType(command);
×
162
                    }
163
                }
164
            } else {
165
                // 0x10 <= command < 0x21
166
                if (command == Commands.V4_SWAP) {
×
167
                    //(bytes memory actions, bytes[] memory encodedParams) = abi.decode(inputs, (bytes, bytes[]));
168
                    // we decode manually to be able to override params?
NEW
169
                    (bytes calldata actions, bytes[] calldata encodedParams) = inputs.decodeActionsRouterParams();
×
NEW
170
                    assert(actions.length == encodedParams.length);
×
171

NEW
172
                    for (uint256 actionIndex = 0; actionIndex < actions.length; actionIndex++) {
×
173
                        uint256 action = uint8(actions[actionIndex]);
×
NEW
174
                        bytes calldata paramsAtIndex = encodedParams[actionIndex];
×
175

176
                        if (action < Actions.SETTLE) {
×
177
                            // no further assertion needed
178
                            //if (action == Actions.SWAP_EXACT_IN) {
179
                            //} else if (action == Actions.SWAP_EXACT_IN_SINGLE) {
180
                            //} else if (action == Actions.SWAP_EXACT_OUT) {
181
                            //} else if (action == Actions.SWAP_EXACT_OUT_SINGLE) {
182
                            //}
183
                        } else {
184
                            if (action == Actions.SETTLE_PAIR) {
×
NEW
185
                                revert UnsupportedAction(action);
×
186
                            } else if (action == Actions.TAKE_PAIR) {
×
NEW
187
                                revert UnsupportedAction(action);
×
188
                            } else if (action == Actions.SETTLE) {
×
189
                                // Currency currency, uint256 amount, bool payerIsUser
NEW
190
                                (Currency currency, uint256 amount,) =
×
191
                                    abi.decode(paramsAtIndex, (Currency, uint256, bool));
NEW
192
                                params.tokensIn = _addUnique(params.tokensIn, Currency.unwrap(currency));
×
NEW
193
                                params.value += Currency.unwrap(currency) == ZERO_ADDRESS ? amount : 0;
×
NEW
194
                                return params;
×
195
                            } else if (action == Actions.TAKE) {
×
196
                                // Currency currency, address recipient, uint256 amount
NEW
197
                                (Currency currency, address recipient,) =
×
198
                                    abi.decode(paramsAtIndex, (Currency, address, uint256));
NEW
199
                                params.tokensOut = _addUnique(params.tokensOut, Currency.unwrap(currency));
×
NEW
200
                                params.recipients = _addUnique(params.recipients, recipient);
×
NEW
201
                                return params;
×
202
                            } else if (action == Actions.CLOSE_CURRENCY) {
×
203
                                // this will either settle or take, so we need to make sure the token is tracked
NEW
204
                                (Currency currency) = paramsAtIndex.decodeCurrency();
×
NEW
205
                                params.tokensOut = _addUnique(params.tokensOut, Currency.unwrap(currency));
×
NEW
206
                                return params;
×
207
                            } else if (action == Actions.CLEAR_OR_TAKE) {
×
208
                                // Currency currency, uint256 amountMax
NEW
209
                                (Currency currency,) = paramsAtIndex.decodeCurrencyAndUint256();
×
NEW
210
                                params.tokensOut = _addUnique(params.tokensOut, Currency.unwrap(currency));
×
NEW
211
                                return params;
×
212
                            } else if (action == Actions.SWEEP) {
×
NEW
213
                                (Currency currency, address to) = paramsAtIndex.decodeCurrencyAndAddress();
×
NEW
214
                                params.tokensOut = _addUnique(params.tokensOut, Currency.unwrap(currency));
×
NEW
215
                                params.recipients = _addUnique(params.recipients, to);
×
NEW
216
                                return params;
×
217
                            }
218
                        }
219
                    }
220
                } else if (command == Commands.V3_POSITION_MANAGER_PERMIT) {
×
NEW
221
                    revert InvalidCommandType(command);
×
222
                } else if (command == Commands.V3_POSITION_MANAGER_CALL) {
×
NEW
223
                    revert InvalidCommandType(command);
×
224
                } else if (command == Commands.V4_POSITION_MANAGER_CALL) {
×
225
                    // v4 liquidity actions must be routed via modifyLiquidities endpoint
NEW
226
                    revert InvalidCommandType(command);
×
227
                } else {
228
                    // placeholder area for commands 0x13-0x20
229
                    revert InvalidCommandType(command);
×
230
                }
231
            }
232
        } else {
233
            // 0x21 <= command
234
            if (command == Commands.EXECUTE_SUB_PLAN) {
×
235
                (bytes memory subCommands, bytes[] memory subInputs) = abi.decode(inputs, (bytes, bytes[]));
×
NEW
236
                return IAUniswapRouter(address(this)).execute(subCommands, subInputs);
×
237
            }
238
        }
NEW
239
        return params;
×
240
    }
241

242
    /// @notice Each liquidity position has its associated hook address, which can be null if no hook is used.
243
    struct Position {
244
        address hook;
245
        uint256 tokenId;
246
        uint256 action;
247
    }
248

249
    function _decodePosmAction(
250
        uint256 action,
251
        bytes calldata actionParams,
252
        IAUniswapRouter.Parameters memory params,
253
        Position[] memory positions
254
    ) internal view returns (IAUniswapRouter.Parameters memory, Position[] memory) {
255
        if (action < Actions.SETTLE) {
13!
256
            if (action == Actions.INCREASE_LIQUIDITY) {
13✔
257
                // uint256 tokenId, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData
258
                (uint256 tokenId,,,,) = actionParams.decodeModifyLiquidityParams();
5✔
259
                positions = _addUniquePosition(positions, Position(ZERO_ADDRESS, tokenId, Actions.INCREASE_LIQUIDITY));
5✔
260
                return (params, positions);
5✔
261
            } else if (action == Actions.INCREASE_LIQUIDITY_FROM_DELTAS) {
8!
NEW
262
                revert UnsupportedAction(action);
×
263
            } else if (action == Actions.DECREASE_LIQUIDITY) {
8✔
264
                (uint256 tokenId,,,,) = actionParams.decodeModifyLiquidityParams();
1✔
265
                positions = _addUniquePosition(positions, Position(ZERO_ADDRESS, tokenId, Actions.DECREASE_LIQUIDITY));
1✔
266
                return (params, positions);
1✔
267
            } else if (action == Actions.MINT_POSITION) {
7✔
268
                // PoolKey calldata poolKey, int24 tickLower, int24 tickUpper, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, address owner, bytes calldata hookData
269
                (PoolKey calldata poolKey,,,,,, address owner,) = actionParams.decodeMintParams();
6✔
270

271
                // as an amount could be null, we want to assert here that both tokens have a price feed
272
                params.tokensOut = _addUnique(params.tokensOut, Currency.unwrap(poolKey.currency0));
6✔
273
                params.tokensOut = _addUnique(params.tokensOut, Currency.unwrap(poolKey.currency1));
6✔
274
                params.recipients = _addUnique(params.recipients, owner);
6✔
275
                positions = _addUniquePosition(positions, Position(address(poolKey.hooks), uniV4Posm().nextTokenId(), Actions.MINT_POSITION));
6✔
276
                return (params, positions);
6✔
277
            } else if (action == Actions.MINT_POSITION_FROM_DELTAS) {
1!
NEW
278
                revert UnsupportedAction(action);
×
279
            } else if (action == Actions.BURN_POSITION) {
1!
280
                // uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData
281
                (uint256 tokenId,,,) = actionParams.decodeBurnParams();
1✔
282
                positions = _addUniquePosition(positions, Position(ZERO_ADDRESS, tokenId, Actions.BURN_POSITION));
1✔
283
                return (params, positions);
1✔
284
            }
285
        } else {
286
            // TODO: verify if should revert following 2 methods, as prob used for migrations only?
NEW
287
            if (action == Actions.SETTLE_PAIR) {
×
288
                // settlement eth value must be retrieved in previous actions
NEW
289
                (Currency currency0, Currency currency1) = actionParams.decodeCurrencyPair();
×
NEW
290
                params.tokensIn = _addUnique(params.tokensIn, Currency.unwrap(currency0));
×
NEW
291
                params.tokensIn = _addUnique(params.tokensIn, Currency.unwrap(currency1));
×
292
                // TODO: how do we get value for pair here?
293
                //params.value += Currency.unwrap(currency0) == ZERO_ADDRESS ? amount : 0;
NEW
294
                return (params, positions);
×
295
            } else if (action == Actions.TAKE_PAIR) {
×
NEW
296
                (Currency currency0, Currency currency1, address recipient) = actionParams.decodeCurrencyPairAndAddress();
×
NEW
297
                params.tokensOut = _addUnique(params.tokensOut, Currency.unwrap(currency0));
×
NEW
298
                params.tokensOut = _addUnique(params.tokensOut, Currency.unwrap(currency1));
×
NEW
299
                params.recipients = _addUnique(params.recipients, recipient);
×
NEW
300
                return (params, positions);
×
301
            } else if (action == Actions.SETTLE) {
×
NEW
302
                (Currency currency, uint256 amount,) = actionParams.decodeCurrencyUint256AndBool();
×
NEW
303
                params.tokensIn = _addUnique(params.tokensIn, Currency.unwrap(currency));
×
NEW
304
                params.value += Currency.unwrap(currency) == ZERO_ADDRESS ? amount : 0;
×
NEW
305
                return (params, positions);
×
306
            } else if (action == Actions.TAKE) {
×
NEW
307
                (Currency currency, address recipient, /*uint256 amount*/) = actionParams.decodeCurrencyAddressAndUint256();
×
NEW
308
                params.tokensOut = _addUnique(params.tokensOut, Currency.unwrap(currency));
×
NEW
309
                params.recipients = _addUnique(params.recipients, recipient);
×
NEW
310
                return (params, positions);
×
311
            } else if (action == Actions.CLOSE_CURRENCY) {
×
312
                // TODO: verify
NEW
313
                revert UnsupportedAction(action);
×
314
            } else if (action == Actions.CLEAR_OR_TAKE) {
×
315
                // no further assertion needed
NEW
316
                (Currency currency,) = actionParams.decodeCurrencyAndUint256();
×
NEW
317
                params.tokensOut = _addUnique(params.tokensOut, Currency.unwrap(currency));
×
NEW
318
                return (params, positions);
×
319
            } else if (action == Actions.SWEEP) {
×
NEW
320
                (Currency currency, address to) = actionParams.decodeCurrencyAndAddress();
×
NEW
321
                params.tokensOut = _addUnique(params.tokensOut, Currency.unwrap(currency));
×
NEW
322
                params.recipients = _addUnique(params.recipients, to);
×
NEW
323
                return (params, positions);
×
324
            } else if (action == Actions.WRAP) {
×
NEW
325
                uint256 amount = actionParams.decodeUint256();
×
NEW
326
                params.tokensOut = _addUnique(params.tokensOut, _wrappedNative);
×
NEW
327
                params.value += amount;
×
NEW
328
                return (params, positions);
×
329
            } else if (action == Actions.UNWRAP) {
×
NEW
330
                params.tokensOut = _addUnique(params.tokensOut, ZERO_ADDRESS);
×
NEW
331
                return (params, positions);
×
332
            }
333
        }
NEW
334
        revert UnsupportedAction(action);
×
335
    }
336

337
    function _addUnique(address[] memory array, address target) private pure returns (address[] memory) {
338
        for (uint256 i = 0; i < array.length; i++) {
18✔
339
            if (array[i] == target) {
6!
NEW
340
                return array; // Already exists, return unchanged array
×
341
            }
342
        }
343
        address[] memory newArray = new address[](array.length + 1);
18✔
344
        for (uint256 i = 0; i < array.length; i++) {
18✔
345
            newArray[i] = array[i];
6✔
346
        }
347
        newArray[array.length] = target;
18✔
348
        return newArray;
18✔
349
    }
350

351
    /// @dev Multiple actions can be executed on the same tokenId, so we add a new position if same tokenId but different action
352
    function _addUniquePosition(Position[] memory array, Position memory pos) private pure returns (Position[] memory) {
353
        for (uint256 i = 0; i < array.length; i++) {
13✔
354
            if (array[i].tokenId == pos.tokenId && array[i].action == pos.action) {
4!
NEW
355
                return array; // Already exists, return unchanged array
×
356
            }
357
        }
358
        Position[] memory newArray = new Position[](array.length + 1);
13✔
359
        for (uint256 i = 0; i < array.length; i++) {
13✔
360
            newArray[i] = array[i];
4✔
361
        }
362

363
        newArray[array.length] = pos;
13✔
364
        return newArray;
13✔
365
    }
366
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc