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: Add Quoter for pool-cl #39

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
100 changes: 100 additions & 0 deletions src/pool-bin/interfaces/IBinQuoter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (C) 2024 PancakeSwap
pragma solidity ^0.8.19;

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

/// @title BinQuoter Interface
/// @notice Supports quoting the delta amounts from exact input or exact output swaps.
/// @notice For each pool also tells you the number of initialized ticks loaded and the sqrt price of the pool after the swap.
/// @dev These functions are not marked view because they rely on calling non-view functions and reverting
/// to compute the result. They are also not gas efficient and should not be called on-chain.
interface IBinQuoter {
error TooLittleReceived();
error TooMuchRequested();
error TransactionTooOld();
error NotSelf();
error NotVault();
error InvalidSwapType();

enum SwapType {
ExactInput,
ExactInputSingle,
ExactOutput,
ExactOutputSingle
}

struct SwapInfo {
SwapType swapType;
address msgSender;
bytes params;
}

struct V4BinExactInputSingleParams {
PoolKey poolKey;
bool swapForY;
address recipient;
uint128 amountIn;
uint128 amountOutMinimum;
bytes hookData;
}

struct V4BinExactInputParams {
Currency currencyIn;
PathKey[] path;
address recipient;
uint128 amountIn;
uint128 amountOutMinimum;
}

struct V4BinExactOutputSingleParams {
PoolKey poolKey;
bool swapForY;
address recipient;
uint128 amountOut;
uint128 amountInMaximum;
bytes hookData;
}

struct V4BinExactOutputParams {
Currency currencyOut;
PathKey[] path;
address recipient;
uint128 amountOut;
uint128 amountInMaximum;
}

/// @notice Swaps `amountIn` of one token for as much as possible of another token
/// @param params The parameters necessary for the swap, encoded as `V4BinExactInputSingleParams` in calldata
/// @return amountOut The amount of the received token
function exactInputSingle(V4BinExactInputSingleParams calldata params, uint256 deadline)
external
payable
returns (uint256 amountOut);

// / @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
// / @param params The parameters necessary for the multi-hop swap, encoded as `V4BinExactInputParams` in calldata
// / @return amountOut The amount of the received token
function exactInput(V4BinExactInputParams calldata params, uint256 deadline)
external
payable
returns (uint256 amountOut);

/// @notice Swaps as little as possible of one token for `amountOut` of another token
/// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
/// @return amountIn The amount of the input token
function exactOutputSingle(V4BinExactOutputSingleParams calldata params, uint256 deadline)
external
payable
returns (uint256 amountIn);

/// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
/// @return amountIn The amount of the input token
function exactOutput(V4BinExactOutputParams calldata params, uint256 deadline)
external
payable
returns (uint256 amountIn);
}
241 changes: 241 additions & 0 deletions src/pool-bin/lens/BinQuoter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (C) 2024 PancakeSwap
pragma solidity ^0.8.19;

import {BalanceDelta} from "pancake-v4-core/src/types/BalanceDelta.sol";
import {PoolKey} from "pancake-v4-core/src/types/PoolKey.sol";
import {ILockCallback} from "pancake-v4-core/src/interfaces/ILockCallback.sol";
import {IVault} from "pancake-v4-core/src/interfaces/IVault.sol";
import {IBinPoolManager} from "pancake-v4-core/src/pool-bin/interfaces/IBinPoolManager.sol";
import {Currency, CurrencyLibrary} from "pancake-v4-core/src/types/Currency.sol";
import {IBinPoolManager} from "pancake-v4-core/src/pool-bin/interfaces/IBinPoolManager.sol";
import {SafeCast} from "pancake-v4-core/src/pool-bin/libraries/math/SafeCast.sol";
import {IBinQuoter} from "../interfaces/IBinQuoter.sol";
import {PathKey, PathKeyLib} from "../libraries/PathKey.sol";

contract BinQuoter is IBinQuoter, ILockCallback {
using CurrencyLibrary for Currency;
using SafeCast for uint128;
using PathKeyLib for PathKey;

IVault public immutable vault;
IBinPoolManager public immutable binPoolManager;

/// @dev Only this address may call this function
modifier selfOnly() {
if (msg.sender != address(this)) revert NotSelf();
_;
}

modifier vaultOnly() {
if (msg.sender != address(vault)) revert NotVault();
_;
}

modifier checkDeadline(uint256 deadline) {
if (block.timestamp > deadline) revert TransactionTooOld();
_;
}

constructor(IVault _vault, IBinPoolManager _binPoolManager) {
vault = _vault;
binPoolManager = _binPoolManager;
}

function exactInputSingle(V4BinExactInputSingleParams calldata params, uint256 deadline)
external
payable
override
checkDeadline(deadline)
returns (uint256 amountOut)
{
amountOut = abi.decode(
vault.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, msg.sender, abi.encode(params)))), (uint256)
);
}

function exactInput(V4BinExactInputParams calldata params, uint256 deadline)
external
payable
override
checkDeadline(deadline)
returns (uint256 amountOut)
{
amountOut = abi.decode(
vault.lock(abi.encode(SwapInfo(SwapType.ExactInput, msg.sender, abi.encode(params)))), (uint256)
);
}

function exactOutputSingle(V4BinExactOutputSingleParams calldata params, uint256 deadline)
external
payable
override
checkDeadline(deadline)
returns (uint256 amountIn)
{
amountIn = abi.decode(
vault.lock(abi.encode(SwapInfo(SwapType.ExactOutputSingle, msg.sender, abi.encode(params)))), (uint256)
);
}

function exactOutput(V4BinExactOutputParams calldata params, uint256 deadline)
external
payable
override
checkDeadline(deadline)
returns (uint256 amountIn)
{
amountIn = abi.decode(
vault.lock(abi.encode(SwapInfo(SwapType.ExactOutput, msg.sender, abi.encode(params)))), (uint256)
);
}

function lockAcquired(bytes calldata data) external override vaultOnly returns (bytes memory) {
SwapInfo memory swapInfo = abi.decode(data, (SwapInfo));

if (swapInfo.swapType == SwapType.ExactInput) {
return abi.encode(
_quoteExactInput(abi.decode(swapInfo.params, (V4BinExactInputParams)))
);
} else if (swapInfo.swapType == SwapType.ExactInputSingle) {
return abi.encode(
_quoteExactInputSingle(abi.decode(swapInfo.params, (V4BinExactInputSingleParams)))
);
} else if (swapInfo.swapType == SwapType.ExactOutput) {
return abi.encode(
_quoteExactOutput(abi.decode(swapInfo.params, (V4BinExactOutputParams)))
);
} else if (swapInfo.swapType == SwapType.ExactOutputSingle) {
return abi.encode(
_quoteExactOutputSingle(abi.decode(swapInfo.params, (V4BinExactOutputSingleParams)))
);
} else {
revert InvalidSwapType();
}
}

/// @dev quote an ExactInput swap on a pool, then revert with the result
function _quoteExactInputSingle(V4BinExactInputSingleParams memory params)
internal
returns (uint256 amountOut)
{
amountOut = uint256(
_swap(
params.poolKey,
params.swapForY,
-(params.amountIn.safeInt128()),
params.hookData
)
);

if (amountOut < params.amountOutMinimum) revert TooLittleReceived();
}

struct V4BinExactInputState {
uint256 pathLength;
PoolKey poolKey;
bool swapForY;
uint128 amountOut;
}

/// @notice Perform a swap with `amountIn` in and ensure at least `amountOutMinimum` out
function _quoteExactInput(V4BinExactInputParams memory params)
internal
returns (uint256 amountOut)
{
V4BinExactInputState memory state;
state.pathLength = params.path.length;

for (uint256 i = 0; i < state.pathLength; i++) {
(state.poolKey, state.swapForY) = params.path[i].getPoolAndSwapDirection(params.currencyIn);

state.amountOut = _swap(
state.poolKey,
state.swapForY,
-(params.amountIn.safeInt128()),
params.path[i].hookData
);

params.amountIn = state.amountOut;
params.currencyIn = params.path[i].intermediateCurrency;
}

if (state.amountOut < params.amountOutMinimum) revert TooLittleReceived();
return uint256(state.amountOut);
}

/// @notice Perform a swap that ensure at least `amountOut` tokens with `amountInMaximum` tokens
function _quoteExactOutputSingle(V4BinExactOutputSingleParams memory params)
internal
returns (uint256 amountIn)
{
amountIn = uint256(
_swap(
params.poolKey,
params.swapForY,
params.amountOut.safeInt128(),
params.hookData
)
);

if (amountIn > params.amountInMaximum) revert TooMuchRequested();
}

struct V4BinExactOutputState {
uint256 pathLength;
PoolKey poolKey;
bool swapForY;
uint128 amountIn;
}

/// @notice Perform a swap that ensure at least `amountOut` tokens with `amountInMaximum` tokens
function _quoteExactOutput(V4BinExactOutputParams memory params)
internal
returns (uint256 amountIn)
{
V4BinExactOutputState memory state;
state.pathLength = params.path.length;

/// @dev Iterate backward from last path to first path
for (uint256 i = state.pathLength; i > 0;) {
// Step 1: Find out poolKey and how much amountIn required to get amountOut
(state.poolKey, state.swapForY) = params.path[i - 1].getPoolAndSwapDirection(params.currencyOut);

state.amountIn = _swap(
state.poolKey,
!state.swapForY,
params.amountOut.safeInt128(),
params.path[i - 1].hookData
);

params.amountOut = state.amountIn;
params.currencyOut = params.path[i - 1].intermediateCurrency;

unchecked {
--i;
}
}

if (state.amountIn > params.amountInMaximum) revert TooMuchRequested();
amountIn = uint256(state.amountIn);
}

/// @dev Execute a swap and return the amounts delta, as well as relevant pool state
/// @notice if amountSpecified > 0, the swap is exactInput, otherwise exactOutput
function _swap(
PoolKey memory poolKey,
bool swapForY,
int128 amountSpecified,
bytes memory hookData
) private returns (uint128 reciprocalAmount) {
BalanceDelta delta = binPoolManager.swap(poolKey, swapForY, amountSpecified, hookData);

if (swapForY) {
/// @dev amountSpecified < 0 indicate exactInput, so reciprocal token is token1 and positive
/// amountSpecified > 0 indicate exactOutput, so reciprocal token is token0 but is negative
reciprocalAmount = amountSpecified < 0 ? uint128(delta.amount1()) : uint128(-delta.amount0());
} else {
reciprocalAmount = amountSpecified < 0 ? uint128(delta.amount0()) : uint128(-delta.amount1());
}
}
}
35 changes: 35 additions & 0 deletions src/pool-bin/libraries/PathKey.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (C) 2024 PancakeSwap
pragma solidity ^0.8.19;

import {Currency} from "pancake-v4-core/src/types/Currency.sol";
import {IHooks} from "pancake-v4-core/src/interfaces/IHooks.sol";
import {IPoolManager} from "pancake-v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "pancake-v4-core/src/types/PoolKey.sol";
import "pancake-v4-core/src/libraries/SafeCast.sol";

struct PathKey {
Currency intermediateCurrency;
uint24 fee;
IHooks hooks;
IPoolManager poolManager;
bytes hookData;
bytes32 parameters;
}

library PathKeyLib {
using SafeCast for int24;

function getPoolAndSwapDirection(PathKey memory params, Currency currencyIn)
internal
pure
returns (PoolKey memory poolKey, bool zeroForOne)
{
(Currency currency0, Currency currency1) = currencyIn < params.intermediateCurrency
? (currencyIn, params.intermediateCurrency)
: (params.intermediateCurrency, currencyIn);

zeroForOne = currencyIn == currency0;
poolKey = PoolKey(currency0, currency1, params.hooks, params.poolManager, params.fee, params.parameters);
}
}
Loading
Loading