Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/multi quoter not isolation #57

Merged
merged 17 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 186 additions & 38 deletions src/MixedQuoter.sol

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions src/interfaces/IMixedQuoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,27 @@ interface IMixedQuoter {
uint256 amountIn
) external returns (uint256 amountOut, uint256 gasEstimate);

/// @notice Returns the amount out received for a given exact input swap without executing the swap
/// @dev All swap results will influence the outcome of subsequent swaps within the same pool
/// @param paths The path of the swap, i.e. each token pair in the path
/// @param actions The actions to take for each pair in the path
/// @param params The params for each action in the path
/// SS_2_EXACT_INPUT_SINGLE params are zero bytes
/// SS_3_EXACT_INPUT_SINGLE params are zero bytes
/// V2_EXACT_INPUT_SINGLE params are zero bytes
/// V3_EXACT_INPUT_SINGLE params are encoded as `uint24 fee`
/// V4_CL_EXACT_INPUT_SINGLE params are encoded as `QuoteMixedV4ExactInputSingleParams`
/// V4_EXACT_INPUT_SINGLE params are encoded as `QuoteMixedV4ExactInputSingleParams`
/// @param amountIn The amount of the first token to swap
/// @return amountOut The amount of the last token that would be received
/// @return gasEstimate The estimate of the gas that the swap consumes
function quoteMixedExactInputNotIsolation(
address[] calldata paths,
bytes calldata actions,
bytes[] calldata params,
uint256 amountIn
) external returns (uint256 amountOut, uint256 gasEstimate);

/// @notice Returns the amount out received for a given exact input but for a swap of a single pool
/// @param params The params for the quote, encoded as `QuoteExactInputSingleParams`
/// tokenIn The token being swapped in
Expand Down
202 changes: 202 additions & 0 deletions src/libraries/MixedQuoterRecorder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.26;

import {PoolKey} from "pancake-v4-core/src/types/PoolKey.sol";

/// @dev Record all token accumulation and swap direction of the transaction for non-v4 pools.
/// @dev Record v4 swap history list for v4 pools.
library MixedQuoterRecorder {
/// @dev uint256 internal constant SWAP_DIRECTION = uint256(keccak256("MIXED_QUOTER_SWAP_DIRECTION")) - 1;
uint256 internal constant SWAP_DIRECTION = 0x420071594cddc2905acbd674683749db4c139d373cc290ba8d49c75296a9f1f9;

/// @dev uint256 internal constant SWAP_TOKEN0_ACCUMULATION = uint256(keccak256("MIXED_QUOTER_SWAP_TOKEN0_ACCUMULATION")) - 1;
uint256 internal constant SWAP_TOKEN0_ACCUMULATION =
0x6859b060ba2f84c00df66c40e8848222c89b2fcc89d5edc84074b9878818ea86;

/// @dev uint256 internal constant SWAP_TOKEN1_ACCUMULATION = uint256(keccak256("MIXED_QUOTER_SWAP_TOKEN1_ACCUMULATION")) - 1;
uint256 internal constant SWAP_TOKEN1_ACCUMULATION =
0x8039a0cfe43b448f327ddf378771d67fba431d4dbc5c8f9531fa80f8a45125e9;

/// @dev uint256 internal SWAP_SS = uint256(keccak256("MIXED_QUOTER_SWAP_SS")) - 1;
uint256 internal constant SWAP_SS = 0x0b6c8b64c3ab4ac7b96ca59ae1454278ba2d62c99873c03d98ae968df846210a;

/// @dev uint256 internal SWAP_V2 = uint256(keccak256("MIXED_QUOTER_SWAP_V2")) - 1;
uint256 internal constant SWAP_V2 = 0xfb50ad98219c08ac49c2f2012c28ee455be42a0adc9a9a5df9e0882de4cf56b5;

/// @dev uint256 internal constant SWAP_V3 = uint256(keccak256("MIXED_QUOTER_SWAP_V3")) - 1;
uint256 internal constant SWAP_V3 = 0xd9d373c35d602baa7832c86d4af60fe46a2e18634c87bebc20d0050afb7633b3;

/// @dev uint256 internal constant SWAP_V4_CL = uint256(keccak256("MIXED_QUOTER_SWAP_V4_CL")) - 1;
uint256 internal constant SWAP_V4_CL = 0x1a7c9a13842b613486d9207eda875c24e33425305b8b8df2e040c19ef2ae3088;

/// @dev uint256 internal constant SWAP_V4_BIN = uint256(keccak256("MIXED_QUOTER_SWAP_V4_BIN")) - 1;
uint256 internal constant SWAP_V4_BIN = 0xea33987d3dc3e2595aa727354eec3d9b92d4061c1331c4a19f9862248f2e1040;

/// @dev uint256 internal constant SWAP_V4_LIST = uint256(keccak256("MIXED_QUOTER_SWAP_V4_LIST")) - 1;
uint256 internal constant SWAP_V4_LIST = 0xecc1e5328541d701c0936cbe59876b89db17cc11dfd146412e855a9a2e1ecbd3;

enum SwapDirection {
NONE,
ZeroForOne,
OneForZero
}

error INVALID_SWAP_DIRECTION();

/// @dev Record and check the swap direction of the transaction.
/// @dev Only support one direction for same non-v4 pool in one transaction.
/// @param poolHash The hash of the pool.
/// @param isZeroForOne The direction of the swap.
function setAndCheckSwapDirection(bytes32 poolHash, bool isZeroForOne) internal {
uint256 swapDirection = isZeroForOne ? uint256(SwapDirection.ZeroForOne) : uint256(SwapDirection.OneForZero);

uint256 currentDirection = getSwapDirection(poolHash);
if (currentDirection == uint256(SwapDirection.NONE)) {
uint256 directionSlot = uint256(keccak256(abi.encode(poolHash, SWAP_DIRECTION)));
assembly ("memory-safe") {
tstore(directionSlot, swapDirection)
}
} else if (currentDirection != swapDirection) {
revert INVALID_SWAP_DIRECTION();
}
}

/// @dev Get the swap direction of the transaction.
/// @param poolHash The hash of the pool.
/// @return swapDirection The direction of the swap.
function getSwapDirection(bytes32 poolHash) internal view returns (uint256 swapDirection) {
uint256 directionSlot = uint256(keccak256(abi.encode(poolHash, SWAP_DIRECTION)));
assembly ("memory-safe") {
swapDirection := tload(directionSlot)
}
}

/// @dev Record the swap token accumulation of the pool.
/// @param poolHash The hash of the pool.
/// @param amountIn The amount of tokenIn.
/// @param amountOut The amount of tokenOut.
/// @param isZeroForOne The direction of the swap.
function setPoolSwapTokenAccumulation(bytes32 poolHash, uint256 amountIn, uint256 amountOut, bool isZeroForOne)
internal
{
uint256 token0Slot = uint256(keccak256(abi.encode(poolHash, SWAP_TOKEN0_ACCUMULATION)));
uint256 token1Slot = uint256(keccak256(abi.encode(poolHash, SWAP_TOKEN1_ACCUMULATION)));
uint256 amount0;
uint256 amount1;
if (isZeroForOne) {
amount0 = amountIn;
amount1 = amountOut;
} else {
amount0 = amountOut;
amount1 = amountIn;
}
assembly ("memory-safe") {
tstore(token0Slot, amount0)
tstore(token1Slot, amount1)
}
}

// @dev Get the swap token accumulation of the pool.
// @param poolHash The hash of the pool.
// @param isZeroForOne The direction of the swap.
// @return accAmountIn The accumulation amount of tokenIn.
// @return accAmountOut The accumulation amount of tokenOut.
function getPoolSwapTokenAccumulation(bytes32 poolHash, bool isZeroForOne)
internal
view
returns (uint256, uint256)
{
uint256 token0Slot = uint256(keccak256(abi.encode(poolHash, SWAP_TOKEN0_ACCUMULATION)));
uint256 token1Slot = uint256(keccak256(abi.encode(poolHash, SWAP_TOKEN1_ACCUMULATION)));
uint256 amount0;
uint256 amount1;
assembly ("memory-safe") {
amount0 := tload(token0Slot)
amount1 := tload(token1Slot)
}
if (isZeroForOne) {
return (amount0, amount1);
} else {
return (amount1, amount0);
}
}

/// @dev Record the swap history list of the v4 pool.
/// @param poolHash The hash of the pool.
/// @param swapListBytes The swap history list bytes.
function setV4PoolSwapList(bytes32 poolHash, bytes memory swapListBytes) internal {
uint256 swapListSlot = uint256(keccak256(abi.encode(poolHash, SWAP_V4_LIST)));
assembly ("memory-safe") {
// save the length of the bytes
tstore(swapListSlot, mload(swapListBytes))

// save data in next slot
let dataSlot := add(swapListSlot, 1)
for { let i := 0 } lt(i, mload(swapListBytes)) { i := add(i, 32) } {
tstore(add(dataSlot, div(i, 32)), mload(add(swapListBytes, add(0x20, i))))
}
}
}

/// @dev Get the swap history list of the v4 pool.
/// @param poolHash The hash of the pool.
/// @return swapListBytes The swap history list bytes.
function getV4PoolSwapList(bytes32 poolHash) internal view returns (bytes memory swapListBytes) {
uint256 swapListSlot = uint256(keccak256(abi.encode(poolHash, SWAP_V4_LIST)));
assembly ("memory-safe") {
// get the length of the bytes
let length := tload(swapListSlot)
swapListBytes := mload(0x40)
mstore(swapListBytes, length)
let dataSlot := add(swapListSlot, 1)
for { let i := 0 } lt(i, length) { i := add(i, 32) } {
mstore(add(swapListBytes, add(0x20, i)), tload(add(dataSlot, div(i, 32))))
}
mstore(0x40, add(swapListBytes, add(0x20, length)))
}
}

/// @dev Get the stable swap pool hash.
/// @param token0 The address of token0.
/// @param token1 The address of token1.
/// @return poolHash The hash of the pool.
function getSSPoolHash(address token0, address token1) internal pure returns (bytes32) {
if (token0 == token1) revert();
(token0, token1) = token0 < token1 ? (token0, token1) : (token1, token0);
return keccak256(abi.encode(token0, token1, SWAP_SS));
}

/// @dev Get the v2 pool hash.
/// @param token0 The address of token0.
/// @param token1 The address of token1.
/// @return poolHash The hash of the pool.
function getV2PoolHash(address token0, address token1) internal pure returns (bytes32) {
if (token0 == token1) revert();
(token0, token1) = token0 < token1 ? (token0, token1) : (token1, token0);
return keccak256(abi.encode(token0, token1, SWAP_V2));
}

/// @dev Get the v3 pool hash.
/// @param token0 The address of token0.
/// @param token1 The address of token1.
/// @param fee The fee of the pool.
function getV3PoolHash(address token0, address token1, uint24 fee) internal pure returns (bytes32) {
if (token0 == token1) revert();
(token0, token1) = token0 < token1 ? (token0, token1) : (token1, token0);
return keccak256(abi.encode(token0, token1, fee, SWAP_V3));
}

/// @dev Get the v4 cl pool hash.
/// @param key The pool key.
/// @return poolHash The hash of the pool.
function getV4CLPoolHash(PoolKey memory key) internal pure returns (bytes32) {
return keccak256(abi.encode(key, SWAP_V4_CL));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious, why not directly key.toId() here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no special reason, just do not want to confuse , and keep same style with other pools

}

/// @dev Get the v4 bin pool hash.
/// @param key The pool key.
/// @return poolHash The hash of the pool.
function getV4BinPoolHash(PoolKey memory key) internal pure returns (bytes32) {
return keccak256(abi.encode(key, SWAP_V4_BIN));
}
}
12 changes: 12 additions & 0 deletions src/pool-bin/interfaces/IBinQuoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ interface IBinQuoter is IQuoter {
external
returns (uint256 amountOut, uint256 gasEstimate);

/// @notice Returns the last swap delta amounts for a given exact input in a list of swap
/// @param params The params for the quote, encoded as `QuoteExactSingleParams[]`
/// poolKey The key for identifying a Bin pool
/// zeroForOne If the swap is from currency0 to currency1
/// exactAmount The desired input amount
/// hookData arbitrary hookData to pass into the associated hooks
/// @return amountOut The last swap output quote for the exactIn swap
/// @return gasEstimate Estimated gas units used for the swap
function quoteExactInputSingleList(QuoteExactSingleParams[] memory params)
external
returns (uint256 amountOut, uint256 gasEstimate);

/// @notice Returns the delta amounts along the swap path for a given exact input swap
/// @param params the params for the quote, encoded as 'QuoteExactParams'
/// currencyIn The input currency of the swap
Expand Down
35 changes: 35 additions & 0 deletions src/pool-bin/lens/BinQuoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ contract BinQuoter is BaseV4Quoter, IBinQuoter {
}
}

/// @inheritdoc IBinQuoter
function quoteExactInputSingleList(QuoteExactSingleParams[] memory params)
external
returns (uint256 amountIn, uint256 gasEstimate)
{
uint256 gasBefore = gasleft();
try vault.lock(abi.encodeCall(this._quoteExactInputSingleList, (params))) {}
catch (bytes memory reason) {
gasEstimate = gasBefore - gasleft();
// Extract the quote from QuoteSwap error, or throw if the quote failed
amountIn = reason.parseQuoteAmount();
}
}

/// @inheritdoc IBinQuoter
function quoteExactInput(QuoteExactParams memory params)
external
Expand Down Expand Up @@ -118,6 +132,27 @@ contract BinQuoter is BaseV4Quoter, IBinQuoter {
amountOut.revertQuote();
}

/// @dev quote ExactInput swap list on a pool, then revert with the result of last swap
function _quoteExactInputSingleList(QuoteExactSingleParams[] calldata swapParamList)
external
selfOnly
returns (bytes memory)
{
uint256 swapLength = swapParamList.length;
if (swapLength == 0) revert();
uint256 amountOut;
for (uint256 i = 0; i < swapLength; i++) {
QuoteExactSingleParams memory params = swapParamList[i];
BalanceDelta swapDelta =
_swap(params.poolKey, params.zeroForOne, -(params.exactAmount.safeInt128()), params.hookData);
if (i == swapLength - 1) {
// the output delta of a swap is positive
amountOut = params.zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0());
}
}
amountOut.revertQuote();
}

/// @dev quote an ExactOutput swap along a path of tokens, then revert with the result
function _quoteExactOutput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) {
uint256 pathLength = params.path.length;
Expand Down
12 changes: 12 additions & 0 deletions src/pool-cl/interfaces/ICLQuoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ interface ICLQuoter is IQuoter {
external
returns (uint256 amountOut, uint256 gasEstimate);

/// @notice Returns the last swap delta amounts for a given exact input in a list of swap
/// @param params The params for the quote, encoded as `QuoteExactSingleParams[]`
/// poolKey The key for identifying a V4 pool
/// zeroForOne If the swap is from currency0 to currency1
/// exactAmount The desired input amount
/// hookData arbitrary hookData to pass into the associated hooks
/// @return amountOut The last swap output quote for the exactIn swap
/// @return gasEstimate Estimated gas units used for the swap
function quoteExactInputSingleList(QuoteExactSingleParams[] memory params)
external
returns (uint256 amountOut, uint256 gasEstimate);

/// @notice Returns the delta amounts along the swap path for a given exact input swap
/// @param params the params for the quote, encoded as 'QuoteExactParams'
/// currencyIn The input currency of the swap
Expand Down
35 changes: 35 additions & 0 deletions src/pool-cl/lens/CLQuoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ contract CLQuoter is ICLQuoter, BaseV4Quoter {
}
}

/// @inheritdoc ICLQuoter
function quoteExactInputSingleList(QuoteExactSingleParams[] memory params)
external
returns (uint256 amountIn, uint256 gasEstimate)
{
uint256 gasBefore = gasleft();
try vault.lock(abi.encodeCall(this._quoteExactInputSingleList, (params))) {}
catch (bytes memory reason) {
gasEstimate = gasBefore - gasleft();
// Extract the quote from QuoteSwap error, or throw if the quote failed
amountIn = reason.parseQuoteAmount();
}
}

/// @inheritdoc ICLQuoter
function quoteExactInput(QuoteExactParams memory params)
external
Expand Down Expand Up @@ -111,6 +125,27 @@ contract CLQuoter is ICLQuoter, BaseV4Quoter {
amountOut.revertQuote();
}

/// @dev quote ExactInput swap list on a pool, then revert with the result of last swap
function _quoteExactInputSingleList(QuoteExactSingleParams[] calldata swapParamList)
external
selfOnly
returns (bytes memory)
{
uint256 swapLength = swapParamList.length;
if (swapLength == 0) revert();
uint256 amountOut;
for (uint256 i = 0; i < swapLength; i++) {
QuoteExactSingleParams memory params = swapParamList[i];
BalanceDelta swapDelta =
_swap(params.poolKey, params.zeroForOne, -int256(int128(params.exactAmount)), params.hookData);
if (i == swapLength - 1) {
// the output delta of a swap is positive
amountOut = params.zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0());
}
}
amountOut.revertQuote();
}

/// @dev quote an ExactOutput swap along a path of tokens, then revert with the result
function _quoteExactOutput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) {
uint256 pathLength = params.path.length;
Expand Down
Loading