From 6f905f4db3657fd7f723c0f037b72fb38a5451e9 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 31 Jul 2024 12:06:23 +0100 Subject: [PATCH 1/3] router decode calldata --- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .../V4Router_ExactIn1Hop_nativeIn.snap | 2 +- .../V4Router_ExactIn1Hop_nativeOut.snap | 2 +- .../V4Router_ExactIn1Hop_oneForZero.snap | 2 +- .../V4Router_ExactIn1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .../V4Router_ExactIn2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .../V4Router_ExactIn3Hops_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle.snap | 2 +- .../V4Router_ExactInputSingle_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle_nativeOut.snap | 2 +- ...Router_ExactOut1Hop_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOut1Hop_nativeOut.snap | 2 +- .../V4Router_ExactOut1Hop_oneForZero.snap | 2 +- .../V4Router_ExactOut1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .../V4Router_ExactOut2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- .../V4Router_ExactOut3Hops_nativeIn.snap | 2 +- .../V4Router_ExactOut3Hops_nativeOut.snap | 2 +- .../V4Router_ExactOutputSingle.snap | 2 +- ...r_ExactOutputSingle_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOutputSingle_nativeOut.snap | 2 +- src/V4Router.sol | 38 +++++++++++++------ src/lens/Quoter.sol | 10 ++--- src/libraries/PathKey.sol | 2 +- 27 files changed, 57 insertions(+), 41 deletions(-) diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index e11616e3..50387cd2 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -6595 \ No newline at end of file +6257 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap index f6f8b33c..c8132152 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap @@ -1 +1 @@ -120991 \ No newline at end of file +120650 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap index 0a624153..b76dc272 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap @@ -1 +1 @@ -120158 \ No newline at end of file +119817 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap index 6f2f9ce5..84dcafbc 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -1 +1 @@ -129030 \ No newline at end of file +128689 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap index 612b68dd..007955f3 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap @@ -1 +1 @@ -135860 \ No newline at end of file +135519 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index 8eb82f04..9c279aa4 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -187212 \ No newline at end of file +187018 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap index c14560ee..bca28775 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap @@ -1 +1 @@ -179175 \ No newline at end of file +178981 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 0409f93c..a95c40b4 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -238593 \ No newline at end of file +238544 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap index 913a30c6..b064bca8 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap @@ -1 +1 @@ -230580 \ No newline at end of file +230531 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index af44576c..6d901210 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -134618 \ No newline at end of file +134054 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap index c73f471b..48ea60fe 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap @@ -1 +1 @@ -119749 \ No newline at end of file +119185 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap index c2705e54..97f1b6c9 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap @@ -1 +1 @@ -118894 \ No newline at end of file +118330 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap index 05f02403..2480c9bd 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap @@ -1 +1 @@ -126794 \ No newline at end of file +126447 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap index cd9382b3..23cedb80 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap @@ -1 +1 @@ -120999 \ No newline at end of file +120652 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap index 1d29c498..c4063c50 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap @@ -1 +1 @@ -129871 \ No newline at end of file +129524 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap index d1018c3e..1c2e72b2 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap @@ -1 +1 @@ -134672 \ No newline at end of file +134325 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index 3ecc5f00..da9eace6 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -186633 \ No newline at end of file +186427 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap index a76127c1..ddd903dc 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap @@ -1 +1 @@ -183556 \ No newline at end of file +183350 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index 45e158a2..57f1972a 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -238638 \ No newline at end of file +238571 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap index 7cba59fe..3cf64027 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap @@ -1 +1 @@ -235585 \ No newline at end of file +235518 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap index 72b7d53b..3bfd0406 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap @@ -1 +1 @@ -229790 \ No newline at end of file +229723 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap index a538db14..ad8ee846 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle.snap @@ -1 +1 @@ -133152 \ No newline at end of file +132588 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap index baf2e400..30390060 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap @@ -1 +1 @@ -125274 \ No newline at end of file +124710 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap index 8cf4fc55..34e548a5 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap @@ -1 +1 @@ -119537 \ No newline at end of file +118973 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index 08366f53..dfc52ba4 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -33,13 +33,29 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { // swap actions and payment actions in different blocks for gas efficiency if (action < Actions.SETTLE) { if (action == Actions.SWAP_EXACT_IN) { - _swapExactInput(abi.decode(params, (IV4Router.ExactInputParams))); + IV4Router.ExactInputParams calldata swapParams; + assembly ("memory-safe") { + swapParams := add(params.offset, calldataload(params.offset)) + } + _swapExactInput(swapParams); } else if (action == Actions.SWAP_EXACT_IN_SINGLE) { - _swapExactInputSingle(abi.decode(params, (IV4Router.ExactInputSingleParams))); + IV4Router.ExactInputSingleParams calldata swapParams; + assembly ("memory-safe") { + swapParams := add(params.offset, calldataload(params.offset)) + } + _swapExactInputSingle(swapParams); } else if (action == Actions.SWAP_EXACT_OUT) { - _swapExactOutput(abi.decode(params, (IV4Router.ExactOutputParams))); + IV4Router.ExactOutputParams calldata swapParams; + assembly ("memory-safe") { + swapParams := add(params.offset, calldataload(params.offset)) + } + _swapExactOutput(swapParams); } else if (action == Actions.SWAP_EXACT_OUT_SINGLE) { - _swapExactOutputSingle(abi.decode(params, (IV4Router.ExactOutputSingleParams))); + IV4Router.ExactOutputSingleParams calldata swapParams; + assembly ("memory-safe") { + swapParams := add(params.offset, calldataload(params.offset)) + } + _swapExactOutputSingle(swapParams); } else { revert UnsupportedAction(action); } @@ -78,7 +94,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { } } - function _swapExactInputSingle(IV4Router.ExactInputSingleParams memory params) private { + function _swapExactInputSingle(IV4Router.ExactInputSingleParams calldata params) private { _swap( params.poolKey, params.zeroForOne, @@ -88,14 +104,14 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { ); } - function _swapExactInput(IV4Router.ExactInputParams memory params) private { + function _swapExactInput(IV4Router.ExactInputParams calldata params) private { unchecked { // Caching for gas savings uint256 pathLength = params.path.length; uint128 amountOut; uint128 amountIn = params.amountIn; Currency currencyIn = params.currencyIn; - PathKey memory pathKey; + PathKey calldata pathKey; for (uint256 i = 0; i < pathLength; i++) { pathKey = params.path[i]; @@ -111,7 +127,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { } } - function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams memory params) private { + function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams calldata params) private { _swap( params.poolKey, params.zeroForOne, @@ -121,14 +137,14 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { ); } - function _swapExactOutput(IV4Router.ExactOutputParams memory params) private { + function _swapExactOutput(IV4Router.ExactOutputParams calldata params) private { unchecked { // Caching for gas savings uint256 pathLength = params.path.length; uint128 amountIn; uint128 amountOut = params.amountOut; Currency currencyOut = params.currencyOut; - PathKey memory pathKey; + PathKey calldata pathKey; for (uint256 i = pathLength; i > 0; i--) { pathKey = params.path[i - 1]; @@ -148,7 +164,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, - bytes memory hookData + bytes calldata hookData ) private returns (int128 reciprocalAmount) { unchecked { BalanceDelta delta = poolManager.swap( diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index dd1cc7d3..fcf63d0c 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -155,7 +155,7 @@ contract Quoter is IQuoter, SafeCallback { } /// @dev quote an ExactInput swap along a path of tokens, then revert with the result - function _quoteExactInput(QuoteExactParams memory params) public selfOnly returns (bytes memory) { + function _quoteExactInput(QuoteExactParams calldata params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; QuoteResult memory result = QuoteResult({ @@ -198,7 +198,7 @@ contract Quoter is IQuoter, SafeCallback { } /// @dev quote an ExactInput swap on a pool, then revert with the result - function _quoteExactInputSingle(QuoteExactSingleParams memory params) public selfOnly returns (bytes memory) { + function _quoteExactInputSingle(QuoteExactSingleParams calldata params) public selfOnly returns (bytes memory) { (, int24 tickBefore,,) = poolManager.getSlot0(params.poolKey.toId()); (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( @@ -223,7 +223,7 @@ contract Quoter is IQuoter, SafeCallback { } /// @dev quote an ExactOutput swap along a path of tokens, then revert with the result - function _quoteExactOutput(QuoteExactParams memory params) public selfOnly returns (bytes memory) { + function _quoteExactOutput(QuoteExactParams calldata params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; QuoteResult memory result = QuoteResult({ @@ -269,7 +269,7 @@ contract Quoter is IQuoter, SafeCallback { } /// @dev quote an ExactOutput swap on a pool, then revert with the result - function _quoteExactOutputSingle(QuoteExactSingleParams memory params) public selfOnly returns (bytes memory) { + function _quoteExactOutputSingle(QuoteExactSingleParams calldata params) public selfOnly returns (bytes memory) { // if no price limit has been specified, cache the output amount for comparison in the swap callback if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; @@ -303,7 +303,7 @@ contract Quoter is IQuoter, SafeCallback { bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, - bytes memory hookData + bytes calldata hookData ) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { deltas = poolManager.swap( poolKey, diff --git a/src/libraries/PathKey.sol b/src/libraries/PathKey.sol index 38884eb4..a286076d 100644 --- a/src/libraries/PathKey.sol +++ b/src/libraries/PathKey.sol @@ -14,7 +14,7 @@ struct PathKey { } library PathKeyLib { - function getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) + function getPoolAndSwapDirection(PathKey calldata params, Currency currencyIn) internal pure returns (PoolKey memory poolKey, bool zeroForOne) From 99bd8cd74fb6886625c678638f45921deabe34c6 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Fri, 2 Aug 2024 13:31:51 +0100 Subject: [PATCH 2/3] add as functions to library --- src/V4Router.sol | 20 +++---------- src/libraries/CalldataDecoder.sol | 49 +++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/V4Router.sol b/src/V4Router.sol index 2649da37..1452a254 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -31,28 +31,16 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { // swap actions and payment actions in different blocks for gas efficiency if (action < Actions.SETTLE) { if (action == Actions.SWAP_EXACT_IN) { - IV4Router.ExactInputParams calldata swapParams; - assembly ("memory-safe") { - swapParams := add(params.offset, calldataload(params.offset)) - } + IV4Router.ExactInputParams calldata swapParams = params.decodeSwapExactInParams(); _swapExactInput(swapParams); } else if (action == Actions.SWAP_EXACT_IN_SINGLE) { - IV4Router.ExactInputSingleParams calldata swapParams; - assembly ("memory-safe") { - swapParams := add(params.offset, calldataload(params.offset)) - } + IV4Router.ExactInputSingleParams calldata swapParams = params.decodeSwapExactInSingleParams(); _swapExactInputSingle(swapParams); } else if (action == Actions.SWAP_EXACT_OUT) { - IV4Router.ExactOutputParams calldata swapParams; - assembly ("memory-safe") { - swapParams := add(params.offset, calldataload(params.offset)) - } + IV4Router.ExactOutputParams calldata swapParams = params.decodeSwapExactOutParams(); _swapExactOutput(swapParams); } else if (action == Actions.SWAP_EXACT_OUT_SINGLE) { - IV4Router.ExactOutputSingleParams calldata swapParams; - assembly ("memory-safe") { - swapParams := add(params.offset, calldataload(params.offset)) - } + IV4Router.ExactOutputSingleParams calldata swapParams = params.decodeSwapExactOutSingleParams(); _swapExactOutputSingle(swapParams); } else { revert UnsupportedAction(action); diff --git a/src/libraries/CalldataDecoder.sol b/src/libraries/CalldataDecoder.sol index e8dcbcfa..06272d69 100644 --- a/src/libraries/CalldataDecoder.sol +++ b/src/libraries/CalldataDecoder.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import {PositionConfig} from "./PositionConfig.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IV4Router} from "../interfaces/IV4Router.sol"; /// @title Library for abi decoding in calldata library CalldataDecoder { @@ -110,6 +111,54 @@ library CalldataDecoder { hookData = params.toBytes(10); } + /// @dev equivalent to: abi.decode(params, (IV4Router.ExactInputParams)) + function decodeSwapExactInParams(bytes calldata params) + internal + pure + returns (IV4Router.ExactInputParams calldata swapParams) + { + // ExactInputParams is a variable length struct so we just have to look up its location + assembly ("memory-safe") { + swapParams := add(params.offset, calldataload(params.offset)) + } + } + + /// @dev equivalent to: abi.decode(params, (IV4Router.ExactInputSingleParams)) + function decodeSwapExactInSingleParams(bytes calldata params) + internal + pure + returns (IV4Router.ExactInputSingleParams calldata swapParams) + { + // ExactInputSingleParams is a variable length struct so we just have to look up its location + assembly ("memory-safe") { + swapParams := add(params.offset, calldataload(params.offset)) + } + } + + /// @dev equivalent to: abi.decode(params, (IV4Router.ExactOutputParams)) + function decodeSwapExactOutParams(bytes calldata params) + internal + pure + returns (IV4Router.ExactOutputParams calldata swapParams) + { + // ExactOutputParams is a variable length struct so we just have to look up its location + assembly ("memory-safe") { + swapParams := add(params.offset, calldataload(params.offset)) + } + } + + /// @dev equivalent to: abi.decode(params, (IV4Router.ExactInputSingleParams)) + function decodeSwapExactOutSingleParams(bytes calldata params) + internal + pure + returns (IV4Router.ExactOutputSingleParams calldata swapParams) + { + // ExactOutputSingleParams is a variable length struct so we just have to look up its location + assembly ("memory-safe") { + swapParams := add(params.offset, calldataload(params.offset)) + } + } + /// @dev equivalent to: abi.decode(params, (Currency)) in calldata function decodeCurrency(bytes calldata params) internal pure returns (Currency currency) { assembly ("memory-safe") { From aaed49438058413298308c790654d300fed9d763 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Fri, 2 Aug 2024 13:52:31 +0100 Subject: [PATCH 3/3] fuzz tests --- test/libraries/CalldataDecoder.t.sol | 81 +++++++++++++++++++++++++--- test/mocks/MockCalldataDecoder.sol | 33 ++++++++++++ 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/test/libraries/CalldataDecoder.t.sol b/test/libraries/CalldataDecoder.t.sol index 08fee4a4..2b4b1e74 100644 --- a/test/libraries/CalldataDecoder.t.sol +++ b/test/libraries/CalldataDecoder.t.sol @@ -2,9 +2,13 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + import {MockCalldataDecoder} from "../mocks/MockCalldataDecoder.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {PathKey} from "../../src/libraries/PathKey.sol"; contract CalldataDecoderTest is Test { MockCalldataDecoder decoder; @@ -83,6 +87,56 @@ contract CalldataDecoderTest is Test { _assertEq(_config, config); } + function test_fuzz_decodeSwapExactInParams(IV4Router.ExactInputParams calldata _swapParams) public view { + bytes memory params = abi.encode(_swapParams); + IV4Router.ExactInputParams memory swapParams = decoder.decodeSwapExactInParams(params); + + assertEq(Currency.unwrap(swapParams.currencyIn), Currency.unwrap(_swapParams.currencyIn)); + assertEq(swapParams.amountIn, _swapParams.amountIn); + assertEq(swapParams.amountOutMinimum, _swapParams.amountOutMinimum); + _assertEq(swapParams.path, _swapParams.path); + } + + function test_fuzz_decodeSwapExactInSingleParams(IV4Router.ExactInputSingleParams calldata _swapParams) + public + view + { + bytes memory params = abi.encode(_swapParams); + IV4Router.ExactInputSingleParams memory swapParams = decoder.decodeSwapExactInSingleParams(params); + + assertEq(swapParams.zeroForOne, _swapParams.zeroForOne); + assertEq(swapParams.amountIn, _swapParams.amountIn); + assertEq(swapParams.amountOutMinimum, _swapParams.amountOutMinimum); + assertEq(swapParams.sqrtPriceLimitX96, _swapParams.sqrtPriceLimitX96); + assertEq(swapParams.hookData, _swapParams.hookData); + _assertEq(swapParams.poolKey, _swapParams.poolKey); + } + + function test_fuzz_decodeSwapExactOutParams(IV4Router.ExactOutputParams calldata _swapParams) public view { + bytes memory params = abi.encode(_swapParams); + IV4Router.ExactOutputParams memory swapParams = decoder.decodeSwapExactOutParams(params); + + assertEq(Currency.unwrap(swapParams.currencyOut), Currency.unwrap(_swapParams.currencyOut)); + assertEq(swapParams.amountOut, _swapParams.amountOut); + assertEq(swapParams.amountInMaximum, _swapParams.amountInMaximum); + _assertEq(swapParams.path, _swapParams.path); + } + + function test_fuzz_decodeSwapExactOutSingleParams(IV4Router.ExactOutputSingleParams calldata _swapParams) + public + view + { + bytes memory params = abi.encode(_swapParams); + IV4Router.ExactOutputSingleParams memory swapParams = decoder.decodeSwapExactOutSingleParams(params); + + assertEq(swapParams.zeroForOne, _swapParams.zeroForOne); + assertEq(swapParams.amountOut, _swapParams.amountOut); + assertEq(swapParams.amountInMaximum, _swapParams.amountInMaximum); + assertEq(swapParams.sqrtPriceLimitX96, _swapParams.sqrtPriceLimitX96); + assertEq(swapParams.hookData, _swapParams.hookData); + _assertEq(swapParams.poolKey, _swapParams.poolKey); + } + function test_fuzz_decodeCurrencyAndAddress(Currency _currency, address __address) public view { bytes memory params = abi.encode(_currency, __address); (Currency currency, address _address) = decoder.decodeCurrencyAndAddress(params); @@ -98,13 +152,28 @@ contract CalldataDecoderTest is Test { assertEq(Currency.unwrap(currency), Currency.unwrap(_currency)); } + function _assertEq(PathKey[] memory path1, PathKey[] memory path2) internal pure { + assertEq(path1.length, path2.length); + for (uint256 i = 0; i < path1.length; i++) { + assertEq(Currency.unwrap(path1[i].intermediateCurrency), Currency.unwrap(path2[i].intermediateCurrency)); + assertEq(path1[i].fee, path2[i].fee); + assertEq(path1[i].tickSpacing, path2[i].tickSpacing); + assertEq(address(path1[i].hooks), address(path2[i].hooks)); + assertEq(path1[i].hookData, path2[i].hookData); + } + } + function _assertEq(PositionConfig memory config1, PositionConfig memory config2) internal pure { - assertEq(Currency.unwrap(config1.poolKey.currency0), Currency.unwrap(config2.poolKey.currency0)); - assertEq(Currency.unwrap(config1.poolKey.currency1), Currency.unwrap(config2.poolKey.currency1)); - assertEq(config1.poolKey.fee, config2.poolKey.fee); - assertEq(config1.poolKey.tickSpacing, config2.poolKey.tickSpacing); - assertEq(address(config1.poolKey.hooks), address(config2.poolKey.hooks)); + _assertEq(config1.poolKey, config2.poolKey); assertEq(config1.tickLower, config2.tickLower); assertEq(config1.tickUpper, config2.tickUpper); } + + function _assertEq(PoolKey memory key1, PoolKey memory key2) internal pure { + assertEq(Currency.unwrap(key1.currency0), Currency.unwrap(key2.currency0)); + assertEq(Currency.unwrap(key1.currency1), Currency.unwrap(key2.currency1)); + assertEq(key1.fee, key2.fee); + assertEq(key1.tickSpacing, key2.tickSpacing); + assertEq(address(key1.hooks), address(key2.hooks)); + } } diff --git a/test/mocks/MockCalldataDecoder.sol b/test/mocks/MockCalldataDecoder.sol index 5a5b1df8..98b28e97 100644 --- a/test/mocks/MockCalldataDecoder.sol +++ b/test/mocks/MockCalldataDecoder.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.24; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {CalldataDecoder} from "../../src/libraries/CalldataDecoder.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; // we need to use a mock contract to make the calls happen in calldata not memory @@ -38,6 +39,38 @@ contract MockCalldataDecoder { return params.decodeBurnParams(); } + function decodeSwapExactInParams(bytes calldata params) + external + pure + returns (IV4Router.ExactInputParams calldata swapParams) + { + return params.decodeSwapExactInParams(); + } + + function decodeSwapExactInSingleParams(bytes calldata params) + external + pure + returns (IV4Router.ExactInputSingleParams calldata swapParams) + { + return params.decodeSwapExactInSingleParams(); + } + + function decodeSwapExactOutParams(bytes calldata params) + external + pure + returns (IV4Router.ExactOutputParams calldata swapParams) + { + return params.decodeSwapExactOutParams(); + } + + function decodeSwapExactOutSingleParams(bytes calldata params) + external + pure + returns (IV4Router.ExactOutputSingleParams calldata swapParams) + { + return params.decodeSwapExactOutSingleParams(); + } + function decodeMintParams(bytes calldata params) external pure