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

Added support for PancakeV3 #7

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ OPTIMISM_RPC=
ARBITRUM_RPC=
DOGECHAIN_RPC=
AVALANCHE_RPC=
BSC_RPC=
DEPLOYER_ADDRESS=
DEPLOYER_PRIVATE_KEY=
ETHERSCAN_API_KEY=
SNOWTRACE_API_KEY=
OPSCAN_API_KEY=
ARBISCAN_API_KEY=
BSCSCAN_API_KEY=
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ Has similar interface to the official [QuoterV2](https://docs.uniswap.org/protoc
| Dogechain | Quickswap | `0x4a6c794192831fB9F4782E61Bec05d6C5cC9F3eA` |
| Polygon | Quickswap | `0x2E0A046481c676235B806Bd004C4b492C850fb34` |
| Arbitrum | Camelot | `0x11E68A3CE9A5A0F95ca9c4B0B8F17849752e24DD` |
| BSC | PancakeswapV3| `0x3dcF802B76105748CC3749CD72a05579b018cD32` |
62 changes: 62 additions & 0 deletions contracts/PancakeV3Quoter/PancakeV3QuoterCore.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;

import "./PancakeV3likeQuoterCore.sol";
import "./lib/TickBitmap.sol";

contract PancakeV3QuoterCore is PancakeV3likeQuoterCore {
function getPoolGlobalState(
address pool
) internal view override returns (GlobalState memory gs) {
gs.fee = uint16(IPancakeV3Pool(pool).fee());
(gs.startPrice, gs.startTick, , , , , ) = IPancakeV3Pool(pool).slot0();
}

function getTickSpacing(
address pool
) internal view override returns (int24) {
return IPancakeV3Pool(pool).tickSpacing();
}

function getLiquidity(
address pool
) internal view override returns (uint128) {
return IPancakeV3Pool(pool).liquidity();
}

function nextInitializedTickWithinOneWord(
address poolAddress,
int24 tick,
int24 tickSpacing,
bool zeroForOne
) internal view override returns (int24 next, bool initialized) {
return
TickBitmap.nextInitializedTickWithinOneWord(
poolAddress,
tick,
tickSpacing,
zeroForOne
);
}

function getTicks(
address pool,
int24 tick
)
internal
view
override
returns (
uint128 liquidityTotal,
int128 liquidityDelta,
uint256 outerFeeGrowth0Token,
uint256 outerFeeGrowth1Token,
int56 outerTickCumulative,
uint160 outerSecondsPerLiquidity,
uint32 outerSecondsSpent,
bool initialized
)
{
return IPancakeV3Pool(pool).ticks(tick);
}
}
93 changes: 93 additions & 0 deletions contracts/PancakeV3Quoter/PancakeV3StaticQuoter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import "@pancakeswap/v3-periphery/contracts/base/PeripheryImmutableState.sol";
import "@pancakeswap/v3-periphery/contracts/libraries/PoolAddress.sol";
import "@pancakeswap/v3-periphery/contracts/libraries/Path.sol";

import "./interfaces/IPancakeV3StaticQuoter.sol";
import "./PancakeV3QuoterCore.sol";

contract PancakeV3StaticQuoter is IPancakeV3StaticQuoter, PancakeV3QuoterCore {
using LowGasSafeMath for uint256;
using LowGasSafeMath for int256;
using SafeCast for uint256;
using SafeCast for int256;
using Path for bytes;

address immutable factory;

constructor(address _factory) {
factory = _factory;
}

function getPool(
address tokenA,
address tokenB,
uint24 fee
) private view returns (IPancakeV3Pool) {
return
IPancakeV3Pool(
PoolAddress.computeAddress(
factory,
PoolAddress.getPoolKey(tokenA, tokenB, fee)
)
);
}

function quoteExactInputSingle(
QuoteExactInputSingleParams memory params
) public view override returns (uint256 amountOut) {
bool zeroForOne = params.tokenIn < params.tokenOut;
IPancakeV3Pool pool = getPool(
params.tokenIn,
params.tokenOut,
params.fee
);
(int256 amount0, int256 amount1) = quote(
address(pool),
zeroForOne,
params.amountIn.toInt256(),
params.sqrtPriceLimitX96 == 0
? (
zeroForOne
? TickMath.MIN_SQRT_RATIO + 1
: TickMath.MAX_SQRT_RATIO - 1
)
: params.sqrtPriceLimitX96
);

return zeroForOne ? uint256(-amount1) : uint256(-amount0);
}

function quoteExactInput(
bytes memory path,
uint256 amountIn
) public view override returns (uint256 amountOut) {
uint256 i = 0;
while (true) {
(address tokenIn, address tokenOut, uint24 fee) = path
.decodeFirstPool();
// the outputs of prior swaps become the inputs to subsequent ones
uint256 _amountOut = quoteExactInputSingle(
QuoteExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: tokenOut,
fee: fee,
amountIn: amountIn,
sqrtPriceLimitX96: 0
})
);
amountIn = _amountOut;
i++;

// decide whether to continue or terminate
if (path.hasMultiplePools()) {
path = path.skipToken();
} else {
return amountIn;
}
}
}
}
219 changes: 219 additions & 0 deletions contracts/PancakeV3Quoter/PancakeV3likeQuoterCore.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;

import "@pancakeswap/v3-core/contracts/libraries/LowGasSafeMath.sol";
import "@pancakeswap/v3-core/contracts/libraries/LiquidityMath.sol";
import "@pancakeswap/v3-core/contracts/libraries/FixedPoint128.sol";
import "@pancakeswap/v3-core/contracts/libraries/SafeCast.sol";
import "@pancakeswap/v3-core/contracts/libraries/TickMath.sol";
import "@pancakeswap/v3-core/contracts/libraries/FullMath.sol";
import "@pancakeswap/v3-core/contracts/libraries/SwapMath.sol";
import "../IUniV3likeQuoterCore.sol";

abstract contract PancakeV3likeQuoterCore {
using LowGasSafeMath for int256;
using SafeCast for uint256;
using SafeCast for int256;

function quote(
address poolAddress,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96
) public view virtual returns (int256 amount0, int256 amount1) {
require(amountSpecified != 0, "amountSpecified cannot be zero");
bool exactInput = amountSpecified > 0;
(int24 tickSpacing, uint16 fee, SwapState memory state) = getInitState(
poolAddress,
zeroForOne,
amountSpecified,
sqrtPriceLimitX96
);
// continue swapping as long as we haven't used the entire input/output and haven't reached the price limit
while (
state.amountSpecifiedRemaining != 0 &&
state.sqrtPriceX96 != sqrtPriceLimitX96
) {
StepComputations memory step;
step.sqrtPriceStartX96 = state.sqrtPriceX96;

(
step.tickNext,
step.initialized,
step.sqrtPriceNextX96
) = nextInitializedTickAndPrice(
poolAddress,
state.tick,
tickSpacing,
zeroForOne
);
// compute values to swap to the target tick, price limit, or point where input/output amount is exhausted
(
state.sqrtPriceX96,
step.amountIn,
step.amountOut,
step.feeAmount
) = SwapMath.computeSwapStep(
state.sqrtPriceX96,
getSqrtRatioTargetX96(
zeroForOne,
step.sqrtPriceNextX96,
sqrtPriceLimitX96
),
state.liquidity,
state.amountSpecifiedRemaining,
fee
);
if (exactInput) {
state.amountSpecifiedRemaining -= (step.amountIn +
step.feeAmount).toInt256();
state.amountCalculated = state.amountCalculated.sub(
step.amountOut.toInt256()
);
} else {
state.amountSpecifiedRemaining += step.amountOut.toInt256();
state.amountCalculated = state.amountCalculated.add(
(step.amountIn + step.feeAmount).toInt256()
);
}
// shift tick if we reached the next price
if (state.sqrtPriceX96 == step.sqrtPriceNextX96) {
// if the tick is initialized, run the tick transition
if (step.initialized) {
(, int128 liquidityNet, , , , , , ) = getTicks(
poolAddress,
step.tickNext
);
// if we're moving leftward, we interpret liquidityNet as the opposite sign
// safe because liquidityNet cannot be type(int128).min
if (zeroForOne) liquidityNet = -liquidityNet;
state.liquidity = LiquidityMath.addDelta(
state.liquidity,
liquidityNet
);
}
state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
} else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) {
// recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96);
}
}

(amount0, amount1) = zeroForOne == exactInput
? (
amountSpecified - state.amountSpecifiedRemaining,
state.amountCalculated
)
: (
state.amountCalculated,
amountSpecified - state.amountSpecifiedRemaining
);
}

function getInitState(
address poolAddress,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96
) internal view returns (int24 ts, uint16 fee, SwapState memory state) {
GlobalState memory gs = getPoolGlobalState(poolAddress);
checkSqrtPriceLimitWithinAllowed(
zeroForOne,
sqrtPriceLimitX96,
gs.startPrice
);
ts = getTickSpacing(poolAddress);
fee = gs.fee;
state = SwapState({
amountSpecifiedRemaining: amountSpecified,
liquidity: getLiquidity(poolAddress),
sqrtPriceX96: gs.startPrice,
amountCalculated: 0,
tick: gs.startTick
});
}

function checkSqrtPriceLimitWithinAllowed(
bool zeroForOne,
uint160 sqrtPriceLimit,
uint160 startPrice
) internal pure {
bool withinAllowed = zeroForOne
? sqrtPriceLimit < startPrice &&
sqrtPriceLimit > TickMath.MIN_SQRT_RATIO
: sqrtPriceLimit > startPrice &&
sqrtPriceLimit < TickMath.MAX_SQRT_RATIO;
require(withinAllowed, "sqrtPriceLimit out of bounds");
}

function nextInitializedTickAndPrice(
address pool,
int24 tick,
int24 tickSpacing,
bool zeroForOne
)
internal
view
returns (int24 tickNext, bool initialized, uint160 sqrtPriceNextX96)
{
(tickNext, initialized) = nextInitializedTickWithinOneWord(
pool,
tick,
tickSpacing,
zeroForOne
);
// ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds
if (tickNext < TickMath.MIN_TICK) tickNext = TickMath.MIN_TICK;
else if (tickNext > TickMath.MAX_TICK) tickNext = TickMath.MAX_TICK;
// get the price for the next tick
sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(tickNext);
}

function getSqrtRatioTargetX96(
bool zeroForOne,
uint160 sqrtPriceNextX96,
uint160 sqrtPriceLimitX96
) internal pure returns (uint160) {
return
(
zeroForOne
? sqrtPriceNextX96 < sqrtPriceLimitX96
: sqrtPriceNextX96 > sqrtPriceLimitX96
)
? sqrtPriceLimitX96
: sqrtPriceNextX96;
}

function getPoolGlobalState(
address pool
) internal view virtual returns (GlobalState memory);

function getLiquidity(address pool) internal view virtual returns (uint128);

function getTickSpacing(address pool) internal view virtual returns (int24);

function nextInitializedTickWithinOneWord(
address poolAddress,
int24 tick,
int24 tickSpacing,
bool zeroForOne
) internal view virtual returns (int24 next, bool initialized);

function getTicks(
address pool,
int24 tick
)
internal
view
virtual
returns (
uint128 liquidityTotal,
int128 liquidityDelta,
uint256 outerFeeGrowth0Token,
uint256 outerFeeGrowth1Token,
int56 outerTickCumulative,
uint160 outerSecondsPerLiquidity,
uint32 outerSecondsSpent,
bool initialized
);
}
Loading