Skip to content
2 changes: 1 addition & 1 deletion snapshots/PermissionedV4RouterTest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"PermissionedV4Router_ExactIn3Hops_OrdinaryTokens_DistinctAssets": "262218",
"PermissionedV4Router_ExactIn3Hops_OrdinaryTokens_DistinctAssets": "262243",
"PermissionedV4Router_ExactInputSingle_OrdinaryTokens": "152683",
"PermissionedV4Router_ExactInputSingle_PermissionedTokens": "257034"
}
74 changes: 37 additions & 37 deletions snapshots/PosMGasTest.json
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
{
"PositionManager_burn_empty": "51259",
"PositionManager_burn_empty_native": "51259",
"PositionManager_burn_nonEmpty_native_withClose": "130336",
"PositionManager_burn_nonEmpty_native_withTakePair": "129724",
"PositionManager_burn_nonEmpty_withClose": "137241",
"PositionManager_burn_nonEmpty_withTakePair": "136630",
"PositionManager_collect_native": "151005",
"PositionManager_collect_sameRange": "159637",
"PositionManager_collect_withClose": "159637",
"PositionManager_collect_withTakePair": "158885",
"PositionManager_decreaseLiquidity_native": "116574",
"PositionManager_decreaseLiquidity_withClose": "125206",
"PositionManager_decreaseLiquidity_withTakePair": "124454",
"PositionManager_decrease_burnEmpty": "140182",
"PositionManager_decrease_burnEmpty_native": "133276",
"PositionManager_decrease_sameRange_allLiquidity": "137922",
"PositionManager_decrease_take_take": "125763",
"PositionManager_increaseLiquidity_erc20_withClose": "163979",
"PositionManager_increaseLiquidity_erc20_withSettlePair": "162855",
"PositionManager_increaseLiquidity_native": "146850",
"PositionManager_increase_autocompoundExactUnclaimedFees": "141404",
"PositionManager_increase_autocompoundExcessFeesCredit": "182872",
"PositionManager_increase_autocompound_clearExcess": "152726",
"PositionManager_mint_native": "370903",
"PositionManager_mint_nativeWithSweep_withClose": "379541",
"PositionManager_mint_nativeWithSweep_withSettlePair": "378637",
"PositionManager_mint_onSameTickLower": "322597",
"PositionManager_mint_onSameTickUpper": "323239",
"PositionManager_mint_sameRange": "248821",
"PositionManager_mint_settleWithBalance_sweep": "424132",
"PositionManager_mint_warmedPool_differentRange": "328615",
"PositionManager_mint_withClose": "425272",
"PositionManager_mint_withSettlePair": "424234",
"PositionManager_multicall_initialize_mint": "461635",
"PositionManager_burn_empty": "51284",
"PositionManager_burn_empty_native": "51284",
"PositionManager_burn_nonEmpty_native_withClose": "130360",
"PositionManager_burn_nonEmpty_native_withTakePair": "129749",
"PositionManager_burn_nonEmpty_withClose": "137266",
"PositionManager_burn_nonEmpty_withTakePair": "136655",
"PositionManager_collect_native": "151036",
"PositionManager_collect_sameRange": "159668",
"PositionManager_collect_withClose": "159668",
"PositionManager_collect_withTakePair": "158916",
"PositionManager_decreaseLiquidity_native": "116605",
"PositionManager_decreaseLiquidity_withClose": "125237",
"PositionManager_decreaseLiquidity_withTakePair": "124485",
"PositionManager_decrease_burnEmpty": "140232",
"PositionManager_decrease_burnEmpty_native": "133326",
"PositionManager_decrease_sameRange_allLiquidity": "137953",
"PositionManager_decrease_take_take": "125794",
"PositionManager_increaseLiquidity_erc20_withClose": "164010",
"PositionManager_increaseLiquidity_erc20_withSettlePair": "162886",
"PositionManager_increaseLiquidity_native": "146881",
"PositionManager_increase_autocompoundExactUnclaimedFees": "141435",
"PositionManager_increase_autocompoundExcessFeesCredit": "182903",
"PositionManager_increase_autocompound_clearExcess": "152757",
"PositionManager_mint_native": "370934",
"PositionManager_mint_nativeWithSweep_withClose": "379572",
"PositionManager_mint_nativeWithSweep_withSettlePair": "378668",
"PositionManager_mint_onSameTickLower": "322628",
"PositionManager_mint_onSameTickUpper": "323270",
"PositionManager_mint_sameRange": "248852",
"PositionManager_mint_settleWithBalance_sweep": "424163",
"PositionManager_mint_warmedPool_differentRange": "328646",
"PositionManager_mint_withClose": "425303",
"PositionManager_mint_withSettlePair": "424265",
"PositionManager_multicall_initialize_mint": "461666",
"PositionManager_permit": "79196",
"PositionManager_permit_secondPosition": "62096",
"PositionManager_permit_secondPosition": "62084",
"PositionManager_permit_twice": "44984",
"PositionManager_subscribe": "88007",
"PositionManager_unsubscribe": "62718",
"position manager initcode hash (without constructor params, as uint256)": "22371412936681345072746904414719301883968703142882969386589328934051094972573",
"positionManager bytecode size": "19965"
"position manager initcode hash (without constructor params, as uint256)": "31566190650096491758703851573109861444803428524943761846064582565711289449983",
"positionManager bytecode size": "20013"
}
26 changes: 13 additions & 13 deletions snapshots/QuoterTest.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"Quoter_exactInputSingle_oneForZero_multiplePositions": "146134",
"Quoter_exactInputSingle_zeroForOne_multiplePositions": "152349",
"Quoter_exactOutputSingle_oneForZero": "79477",
"Quoter_exactOutputSingle_zeroForOne": "84722",
"Quoter_quoteExactInput_oneHop_1TickLoaded": "123688",
"Quoter_quoteExactInput_oneHop_initializedAfter": "148320",
"Quoter_quoteExactInput_oneHop_startingInitialized": "81595",
"Quoter_quoteExactInput_twoHops": "206640",
"Quoter_quoteExactOutput_oneHop_1TickLoaded": "123181",
"Quoter_quoteExactOutput_oneHop_2TicksLoaded": "153836",
"Quoter_quoteExactOutput_oneHop_initializedAfter": "123208",
"Quoter_quoteExactOutput_oneHop_startingInitialized": "99502",
"Quoter_quoteExactOutput_twoHops": "206368"
"Quoter_exactInputSingle_oneForZero_multiplePositions": "146185",
"Quoter_exactInputSingle_zeroForOne_multiplePositions": "152407",
"Quoter_exactOutputSingle_oneForZero": "79521",
"Quoter_exactOutputSingle_zeroForOne": "84774",
"Quoter_quoteExactInput_oneHop_1TickLoaded": "123746",
"Quoter_quoteExactInput_oneHop_initializedAfter": "148371",
"Quoter_quoteExactInput_oneHop_startingInitialized": "81646",
"Quoter_quoteExactInput_twoHops": "206749",
"Quoter_quoteExactOutput_oneHop_1TickLoaded": "123262",
"Quoter_quoteExactOutput_oneHop_2TicksLoaded": "153917",
"Quoter_quoteExactOutput_oneHop_initializedAfter": "123289",
"Quoter_quoteExactOutput_oneHop_startingInitialized": "99583",
"Quoter_quoteExactOutput_twoHops": "206524"
}
38 changes: 19 additions & 19 deletions snapshots/V4RouterTest.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
{
"V4Router_Bytecode": "10803",
"V4Router_ExactIn1Hop_nativeIn": "123054",
"V4Router_ExactIn1Hop_nativeOut": "121521",
"V4Router_ExactIn1Hop_oneForZero": "130393",
"V4Router_ExactIn1Hop_zeroForOne": "136918",
"V4Router_ExactIn2Hops": "194220",
"V4Router_ExactIn2Hops_nativeIn": "180356",
"V4Router_ExactIn3Hops": "251550",
"V4Router_ExactIn3Hops_nativeIn": "237686",
"V4Router_Bytecode": "10928",
"V4Router_ExactIn1Hop_nativeIn": "123079",
"V4Router_ExactIn1Hop_nativeOut": "121546",
"V4Router_ExactIn1Hop_oneForZero": "130418",
"V4Router_ExactIn1Hop_zeroForOne": "136943",
"V4Router_ExactIn2Hops": "194245",
"V4Router_ExactIn2Hops_nativeIn": "180381",
"V4Router_ExactIn3Hops": "251575",
"V4Router_ExactIn3Hops_nativeIn": "237711",
"V4Router_ExactInputSingle": "135038",
"V4Router_ExactInputSingle_nativeIn": "121174",
"V4Router_ExactInputSingle_nativeOut": "119619",
"V4Router_ExactOut1Hop_nativeIn_sweepETH": "129686",
"V4Router_ExactOut1Hop_nativeOut": "123107",
"V4Router_ExactOut1Hop_oneForZero": "131979",
"V4Router_ExactOut1Hop_zeroForOne": "136604",
"V4Router_ExactOut2Hops": "193342",
"V4Router_ExactOut2Hops_nativeIn": "186424",
"V4Router_ExactOut3Hops": "250123",
"V4Router_ExactOut3Hops_nativeIn": "243205",
"V4Router_ExactOut3Hops_nativeOut": "227374",
"V4Router_ExactOut1Hop_nativeIn_sweepETH": "129727",
"V4Router_ExactOut1Hop_nativeOut": "123148",
"V4Router_ExactOut1Hop_oneForZero": "132020",
"V4Router_ExactOut1Hop_zeroForOne": "136645",
"V4Router_ExactOut2Hops": "193383",
"V4Router_ExactOut2Hops_nativeIn": "186465",
"V4Router_ExactOut3Hops": "250164",
"V4Router_ExactOut3Hops_nativeIn": "243246",
"V4Router_ExactOut3Hops_nativeOut": "227415",
"V4Router_ExactOutputSingle": "134618",
"V4Router_ExactOutputSingle_nativeIn_sweepETH": "127700",
"V4Router_ExactOutputSingle_nativeOut": "121134",
"router initcode hash (without constructor params, as uint256)": "114729708465987648700921406537732645444618493910742794723401238925364218137616"
"router initcode hash (without constructor params, as uint256)": "1934144615712005979990355159924719464454373626622928348108804334780921485626"
}
11 changes: 9 additions & 2 deletions src/V4Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver {
unchecked {
// Caching for gas savings
uint256 pathLength = params.path.length;
if (pathLength == 0) revert EmptyPath();
uint128 amountOut;
Currency currencyIn = params.currencyIn;
uint128 amountIn = _mapSwapAmount(params.amountIn);
Expand Down Expand Up @@ -151,12 +152,15 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver {
}
BalanceDelta delta = _swap(params.poolKey, params.zeroForOne, int256(uint256(amountOut)), params.hookData);
// exact output is all-or-nothing: a pool can deliver less than requested if it runs out of
// liquidity before the price limit. Reverting on a shortfall keeps "exact output" exact;
// over-delivery (possible only via hook pools) is allowed.
// liquidity before the price limit, and never more, since v4-core folds a hook's specified-side
// delta into the amount it swaps and afterSwap can only adjust the unspecified side. Reverting
// on a shortfall keeps "exact output" exact.
uint128 amountOutActual = _swapOutput(delta, params.zeroForOne);
if (amountOutActual < amountOut) revert V4ExactOutputUnfilled(amountOut, amountOutActual);
uint128 amountIn = _swapInput(delta, params.zeroForOne);
if (amountIn > params.amountInMaximum) revert V4TooMuchRequested(params.amountInMaximum, amountIn);
// A zero-cost (fully hook-funded) swap leaves pre-funded input in the router; plans using
// payerIsUser=false or native input must return the full remaining input currency balance.
// a hook can fund the whole input, leaving a positive output against a zero input. The realized
// price is then infinite and clears every finite bound, so skip the division rather than panic.
if (params.minHopPriceX36 != 0 && amountIn != 0) {
Expand All @@ -171,6 +175,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver {
unchecked {
// Caching for gas savings
uint256 pathLength = params.path.length;
if (pathLength == 0) revert EmptyPath();
uint128 amountIn;
uint128 amountOut = _mapSwapAmount(params.amountOut);
Currency currencyOut = params.currencyOut;
Expand Down Expand Up @@ -211,6 +216,8 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver {
// PoolManager rejects. The untouched currencies carry no delta, so settlement is a
// no-op for them and amountIn of 0 trivially clears amountInMaximum below.
// The upstream pools are never swapped, so their hooks never run.
// A zero-cost (fully hook-funded) route leaves pre-funded input in the router; plans using
// payerIsUser=false or native input must return the full remaining input currency balance.
if (amountIn == 0) break;
amountOut = amountIn;
currencyOut = pathKey.intermediateCurrency;
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/IV4Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ interface IV4Router is IImmutableState {
error V4TooMuchRequestedPerHopSingle(uint256 minPrice, uint256 price);
/// @notice Emitted when the length of the per-hop minimum price array is not zero and not equal to the path length
error InvalidHopPriceLength();
/// @notice Emitted when a multi-hop swap is given an empty path
error EmptyPath();
/// @notice Emitted when an exactOutput swap (or hop) delivers less than the requested amount, e.g. a
/// pool runs out of liquidity before the price limit. Exact output is all-or-nothing.
error V4ExactOutputUnfilled(uint256 amountOutRequested, uint256 amountOutReceived);
Expand Down
26 changes: 18 additions & 8 deletions src/lens/V4Quoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {IV4Quoter} from "../interfaces/IV4Quoter.sol";
import {PathKey} from "../libraries/PathKey.sol";
import {QuoterRevert} from "../libraries/QuoterRevert.sol";
Expand All @@ -19,6 +20,7 @@ import {IMsgSender} from "../interfaces/IMsgSender.sol";
/// to compute the result. They are also not gas efficient and should not be called on-chain.
contract V4Quoter is IV4Quoter, BaseV4Quoter {
using QuoterRevert for *;
using SafeCast for *;

constructor(IPoolManager _poolManager) BaseV4Quoter(_poolManager) {}

Expand Down Expand Up @@ -100,9 +102,9 @@ contract V4Quoter is IV4Quoter, BaseV4Quoter {
pathKey = params.path[i];
(PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(inputCurrency);

swapDelta = _swap(poolKey, zeroForOne, -int256(int128(amountIn)), pathKey.hookData);
swapDelta = _swap(poolKey, zeroForOne, -int256(uint256(amountIn)), pathKey.hookData);

amountIn = zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0());
amountIn = zeroForOne ? swapDelta.amount1().toUint128() : swapDelta.amount0().toUint128();
inputCurrency = pathKey.intermediateCurrency;
}
// amountIn after the loop actually holds the amountOut of the trade
Expand All @@ -112,10 +114,11 @@ contract V4Quoter is IV4Quoter, BaseV4Quoter {
/// @dev external function called within the _unlockCallback, to simulate a single-hop exact input swap, then revert with the result
function _quoteExactInputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) {
BalanceDelta swapDelta =
_swap(params.poolKey, params.zeroForOne, -int256(int128(params.exactAmount)), params.hookData);
_swap(params.poolKey, params.zeroForOne, -int256(uint256(params.exactAmount)), params.hookData);

// the output delta of a swap is positive
uint256 amountOut = params.zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0());
// The output delta is positive for ordinary pools. A hook taking more than the whole output can drive it
// negative, which the cast rejects.
uint256 amountOut = params.zeroForOne ? swapDelta.amount1().toUint128() : swapDelta.amount0().toUint128();
amountOut.revertQuote();
}

Expand All @@ -133,7 +136,11 @@ contract V4Quoter is IV4Quoter, BaseV4Quoter {

swapDelta = _swap(poolKey, !oneForZero, int256(uint256(amountOut)), pathKey.hookData);

amountOut = oneForZero ? uint128(-swapDelta.amount1()) : uint128(-swapDelta.amount0());
amountOut = oneForZero
? uint256(-int256(swapDelta.amount1())).toUint128()
: uint256(-int256(swapDelta.amount0())).toUint128();

if (amountOut == 0) break;

outputCurrency = pathKey.intermediateCurrency;
}
Expand All @@ -146,8 +153,11 @@ contract V4Quoter is IV4Quoter, BaseV4Quoter {
BalanceDelta swapDelta =
_swap(params.poolKey, params.zeroForOne, int256(uint256(params.exactAmount)), params.hookData);

// the input delta of a swap is negative so we must flip it
uint256 amountIn = params.zeroForOne ? uint128(-swapDelta.amount0()) : uint128(-swapDelta.amount1());
// The input delta is negative for ordinary pools. A hook can fund it exactly, leaving zero; an overfunded
// positive delta is rejected by the cast.
uint256 amountIn = params.zeroForOne
? uint256(-int256(swapDelta.amount0())).toUint128()
: uint256(-int256(swapDelta.amount1())).toUint128();
amountIn.revertQuote();
}

Expand Down
5 changes: 5 additions & 0 deletions src/libraries/CalldataDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@ library CalldataDecoder {
uint256 length;
assembly ("memory-safe") {
// The offset of the `_arg`-th element is `32 * arg`, which stores the offset of the length pointer.
// That head word must itself lie inside `_bytes`: compare in whole words so a huge `_arg` cannot wrap.
if iszero(gt(div(_bytes.length, 0x20), _arg)) {
mstore(0, SLICE_ERROR_SELECTOR)
revert(0x1c, 4)
}
// shl(5, x) is equivalent to mul(32, x)
let lengthPtr :=
add(_bytes.offset, and(calldataload(add(_bytes.offset, shl(5, _arg))), OFFSET_OR_LENGTH_MASK))
Expand Down
Loading
Loading