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

Add support for PancakeSwap V3 #11

Merged
merged 10 commits into from
Apr 1, 2024
Merged
14 changes: 12 additions & 2 deletions .github/workflows/foundry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
env:
FOUNDRY_PROFILE: ci
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
BNB_RPC_URL: ${{ secrets.BNB_RPC_URL }}

jobs:
check:
Expand Down Expand Up @@ -41,7 +42,16 @@ jobs:
forge build
id: build

- name: Run Forge tests
- name: Run Forge tests on Ethereum mainnet
run: |
forge test -vvv
id: test
env:
CHAIN_ID: 1
id: testMainnet

- name: Run Forge tests on BNB mainnet
run: |
forge test -vvv
env:
CHAIN_ID: 56
id: testBNB
15 changes: 15 additions & 0 deletions contracts/EphemeralPoolPositions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ contract EphemeralPoolPositions is PoolUtils {
}
}

function getPositionsSlot() internal pure virtual returns (uint256) {
// Storage slot of the `positions` mapping in UniswapV3Pool.
return 7;
}

/// @notice Get liquidity positions in a pool
/// @dev Public function to expose the abi for easier decoding using TypeChain
/// @param pool The address of the pool for which to fetch the tick bitmap
/// @param keys The position keys to fetch
/// @return slots An array of storage slots and their raw data
function getPositions(V3PoolCallee pool, PositionKey[] memory keys) public payable returns (Slot[] memory slots) {
unchecked {
uint256 POSITIONS_SLOT = getPositionsSlot();
uint256 length = keys.length;
// each position occupies 4 storage slots
slots = new Slot[](length << 2);
Expand Down Expand Up @@ -50,3 +56,12 @@ contract EphemeralPoolPositions is PoolUtils {
}
}
}

contract EphemeralPCSV3PoolPositions is EphemeralPoolPositions {
constructor(V3PoolCallee pool, PositionKey[] memory keys) payable EphemeralPoolPositions(pool, keys) {}

function getPositionsSlot() internal pure override returns (uint256) {
// Storage slot of the `positions` mapping in PancakeSwapV3Pool.
return 8;
}
}
85 changes: 84 additions & 1 deletion contracts/EphemeralPoolSlots.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Pool.sol";
import "./PoolUtils.sol";

/// @notice A lens for fetching static state variables in a Uniswap v3 pool without deployment
Expand Down Expand Up @@ -72,7 +73,89 @@ contract EphemeralPoolSlots is PoolUtils {
observation := or(shl(32, and(0xffffffffffffff, tickCumulative)), observation)
observation := or(blockTimestamp, observation)
}
slots[i + 5] = Slot(i + OBSERVATIONS_SLOT, observation);
// UniswapV3Pool's `observations` struct array starts at slot 8.
slots[i + 5] = Slot(i + 8, observation);
}
}
}
}

/// @notice A lens for fetching static state variables in a PancakeSwap v3 pool without deployment
/// @author Aperture Finance
/// @dev The return data can be accessed externally by `eth_call` without a `to` address or internally by catching the
/// revert data, and decoded by `abi.decode(data, (Slot[]))`
contract EphemeralPCSV3PoolSlots is PoolUtils {
constructor(V3PoolCallee pool) payable {
Slot[] memory slots = getSlots(pool);
bytes memory returnData = abi.encode(slots);
assembly ("memory-safe") {
revert(add(returnData, 0x20), mload(returnData))
}
}

/// @notice Get the static storage slots of a pool
/// @dev Public function to expose the abi for easier decoding using TypeChain
/// @param pool The PancakeSwap v3 pool
/// @return slots An array of storage slots and their raw data
function getSlots(V3PoolCallee pool) public payable returns (Slot[] memory slots) {
unchecked {
uint256 length;
{
(
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint32 feeProtocol,
bool unlocked
) = IPancakeV3Pool(V3PoolCallee.unwrap(pool)).slot0();
// PancakeV3Pool's slot0() fields actually span two storage slots (slot 0 and 1) as a result of their changing `feeProtocol` from uint8 to uint32.
// The first 5 fields are packed into slot 0, and the last 2 fields (`feeProtocol` and `unlocked`) are packed into slot 1.
// See https://evm.storage/eth/19541394/0x6ca298d2983ab03aa1da7679389d955a4efee15c/slot0#map for a visual representation.
uint256 slot0;
uint256 slot1;
assembly {
slot0 := shl(216, observationCardinalityNext)
slot0 := or(shl(200, observationCardinality), slot0)
slot0 := or(shl(184, observationIndex), slot0)
slot0 := or(shl(160, and(0xffffff, tick)), slot0)
slot0 := or(sqrtPriceX96, slot0)
slot1 := shl(32, unlocked)
slot1 := or(feeProtocol, slot1)
}
length = observationCardinality;
slots = new Slot[](length + 6);
slots[0] = Slot(0, slot0);
slots[1] = Slot(1, slot1);
}
slots[2] = Slot(2, pool.feeGrowthGlobal0X128());
slots[3] = Slot(3, pool.feeGrowthGlobal1X128());
{
(uint128 token0, uint128 token1) = pool.protocolFees();
uint256 slot3;
assembly {
slot3 := or(shl(128, token1), token0)
}
slots[4] = Slot(4, slot3);
}
slots[5] = Slot(5, pool.liquidity());
for (uint256 i; i < length; ++i) {
(
uint32 blockTimestamp,
int56 tickCumulative,
uint160 secondsPerLiquidityCumulativeX128,
bool initialized
) = pool.observations(i);
uint256 observation;
assembly {
observation := shl(248, initialized)
observation := or(shl(88, secondsPerLiquidityCumulativeX128), observation)
observation := or(shl(32, and(0xffffffffffffff, tickCumulative)), observation)
observation := or(blockTimestamp, observation)
}
// PancakeSwapV3Pool's `observations` struct array starts at slot 9.
slots[i + 6] = Slot(i + 9, observation);
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions contracts/EphemeralPoolTickBitmap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ contract EphemeralPoolTickBitmap is PoolUtils {
}
}

function getTickBitmapSlot() internal pure virtual returns (uint256) {
// Storage slot of the `tickBitmap` mapping in UniswapV3Pool.
return 6;
}

/// @notice Get the tick bitmap for a pool
/// @dev Public function to expose the abi for easier decoding using TypeChain
/// @param pool The address of the pool for which to fetch the tick bitmap
Expand All @@ -25,6 +30,7 @@ contract EphemeralPoolTickBitmap is PoolUtils {
// checks that the pool exists
int24 tickSpacing = IUniswapV3Pool(V3PoolCallee.unwrap(pool)).tickSpacing();
(int16 wordPosLower, int16 wordPosUpper) = getWordPositions(TickMath.MIN_TICK, TickMath.MAX_TICK, tickSpacing);
uint256 TICKBITMAP_SLOT = getTickBitmapSlot();
unchecked {
// cache the bitmap and calculate the number of populated ticks
slots = new Slot[](uint16(wordPosUpper - wordPosLower + 1));
Expand All @@ -42,3 +48,12 @@ contract EphemeralPoolTickBitmap is PoolUtils {
}
}
}

contract EphemeralPCSV3PoolTickBitmap is EphemeralPoolTickBitmap {
constructor(V3PoolCallee pool) payable EphemeralPoolTickBitmap(pool) {}

function getTickBitmapSlot() internal pure override returns (uint256) {
// Storage slot of the `tickBitmap` mapping in PancakeSwapV3Pool.
return 7;
}
}
19 changes: 19 additions & 0 deletions contracts/EphemeralPoolTicks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ contract EphemeralPoolTicks is PoolUtils {
}
}

function getTicksSlot() internal pure virtual returns (uint256) {
// Storage slot of the `ticks` mapping in UniswapV3Pool.
return 5;
}

/// @notice Get all the tick data for the populated ticks from tickLower to tickUpper
/// @dev Public function to expose the abi for easier decoding using TypeChain
/// @param pool The address of the pool for which to fetch populated tick data
Expand Down Expand Up @@ -60,6 +65,7 @@ contract EphemeralPoolTicks is PoolUtils {
uint256 idx
) internal view returns (uint256) {
unchecked {
uint256 TICKS_SLOT = getTicksSlot();
for (uint256 bitPos; bitPos < 256; ++bitPos) {
//slither-disable-next-line incorrect-shift
if (bitmap & (1 << bitPos) != 0) {
Expand Down Expand Up @@ -107,3 +113,16 @@ contract EphemeralPoolTicks is PoolUtils {
}
}
}

contract EphemeralPCSV3PoolTicks is EphemeralPoolTicks {
constructor(
V3PoolCallee pool,
int24 tickLower,
int24 tickUpper
) payable EphemeralPoolTicks(pool, tickLower, tickUpper) {}

function getTicksSlot() internal pure override returns (uint256) {
// Storage slot of the `ticks` mapping in PancakeSwapV3Pool.
return 6;
}
}
12 changes: 0 additions & 12 deletions contracts/PoolUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,6 @@ abstract contract PoolUtils {

uint256 internal constant Q128 = 1 << 128;

/// @dev Storage slot of the `ticks` mapping
uint256 internal constant TICKS_SLOT = 5;

/// @dev Storage slot of the `tickBitmap` mapping
uint256 internal constant TICKBITMAP_SLOT = 6;

/// @dev Storage slot of the `positions` mapping
uint256 internal constant POSITIONS_SLOT = 7;

/// @dev Storage slot of the `observations` mapping
uint256 internal constant OBSERVATIONS_SLOT = 8;

/// @notice Struct for a storage slot and its raw data
struct Slot {
uint256 slot;
Expand Down
10 changes: 7 additions & 3 deletions contracts/PositionUtils.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {INonfungiblePositionManager as INPM} from "@aperture_finance/uni-v3-lib/src/interfaces/INonfungiblePositionManager.sol";
import {INonfungiblePositionManager as INPM, IPCSV3NonfungiblePositionManager as IPCSV3NPM} from "@aperture_finance/uni-v3-lib/src/interfaces/INonfungiblePositionManager.sol";
import {NPMCaller, PositionFull} from "@aperture_finance/uni-v3-lib/src/NPMCaller.sol";
import {PoolAddress} from "@aperture_finance/uni-v3-lib/src/PoolAddress.sol";
import {PoolAddressPancakeSwapV3} from "@aperture_finance/uni-v3-lib/src/PoolAddressPancakeSwapV3.sol";
import {IUniswapV3PoolState, V3PoolCallee} from "@aperture_finance/uni-v3-lib/src/PoolCaller.sol";
import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import {ERC20Callee} from "./libraries/ERC20Caller.sol";
import {PoolUtils} from "./PoolUtils.sol";

Expand All @@ -14,7 +16,9 @@ struct Slot0 {
uint16 observationIndex;
uint16 observationCardinality;
uint16 observationCardinalityNext;
uint8 feeProtocol;
// `feeProtocol` is of type uint8 in Uniswap V3, and uint32 in PancakeSwap V3.
// We use uint32 here as this can hold both uint8 and uint32.
uint32 feeProtocol;
bool unlocked;
}

Expand Down Expand Up @@ -49,7 +53,7 @@ abstract contract PositionUtils is PoolUtils {
state.tokenId = tokenId;
PositionFull memory position = state.position;
V3PoolCallee pool = V3PoolCallee.wrap(
PoolAddress.computeAddressSorted(NPMCaller.factory(npm), position.token0, position.token1, position.fee)
IUniswapV3Factory(NPMCaller.factory(npm)).getPool(position.token0, position.token1, position.fee)
);
state.activeLiquidity = pool.liquidity();
slot0InPlace(pool, state.slot0);
Expand Down
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ runs = 16

[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
bnb_smart_chain = "${BNB_RPC_URL}"

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
28 changes: 15 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "aperture-lens",
"version": "0.6.0",
"version": "1.0.0",
"description": "Contains ephemeral lens contracts that can be called without deployment and their interfaces in various Web3 libraries.",
"author": "Aperture Finance <engineering@aperture.finance>",
"license": "Apache-2.0",
Expand Down Expand Up @@ -47,30 +47,32 @@
"typechain": "hardhat typechain"
},
"dependencies": {
"@aperture_finance/uni-v3-lib": "^1.2.1",
"@openzeppelin/contracts": "^5.0.1",
"@aperture_finance/uniswap-v3-automation-sdk": "3.0.0-alpha",
gnarlycow marked this conversation as resolved.
Show resolved Hide resolved
"@aperture_finance/uni-v3-lib": "^2.0.1",
"@openzeppelin/contracts": "^5.0.2",
"ethers": "5.7.2",
"viem": "^2.7.9"
"viem": "^2.9.6"
},
"devDependencies": {
"@aperture_finance/uniswap-v3-automation-sdk": "^1.13.1",
"@ethersproject/abi": "5.7.0",
"@ethersproject/providers": "5.7.2",
"@nomicfoundation/hardhat-foundry": "^1.1.1",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@pancakeswap/v3-sdk": "^3.8.0",
"@typechain/ethers-v5": "^11.1.2",
"@typechain/hardhat": "^7.0.0",
"@types/chai": "^4.3.11",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.3.14",
"@types/mocha": "^10.0.6",
"@types/node": "^20.10.6",
"chai": "^4.3.10",
"hardhat": "^2.19.4",
"mocha": "^10.2.0",
"prettier": "^3.1.1",
"@types/node": "^20.11.30",
"@uniswap/v3-sdk": "^3.11.0",
"chai": "^4.4.1",
"hardhat": "^2.22.2",
"mocha": "^10.4.0",
"prettier": "^3.2.5",
"prettier-plugin-solidity": "^1.3.1",
"ts-node": "^10.9.2",
"typechain": "^8.3.2",
"typescript": "^5.3.3"
"typescript": "^5.4.3"
},
"prettier": {
"plugins": [
Expand Down
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@aperture_finance/uni-v3-lib/=node_modules/@aperture_finance/uni-v3-lib/
@openzeppelin/=node_modules/@openzeppelin/
@pancakeswap/=node_modules/@pancakeswap/
@uniswap/=node_modules/@uniswap/
forge-std/=lib/forge-std/src/
solady/=node_modules/solady/
Loading
Loading