Skip to content

Commit

Permalink
feat: add Panoptic adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
dyedm1 committed Dec 19, 2024
1 parent 1c0314b commit 9cd2e6a
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 0 deletions.
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"graphql-request": "^4.0.0",
"hi-base32": "^0.5.1",
"js-sha512": "^0.8.0",
"jsbi": "^4.3.0",
"limiter": "2.1.0",
"miscreant": "^0.3.2",
"p-limit": "^3.1.0",
Expand Down
249 changes: 249 additions & 0 deletions projects/panoptic/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
const sdk = require('@defillama/sdk')
const { getLogs } = require('../helper/cache/getLogs')
const JSBI = require('jsbi');


const FACTORY = '0x000000000000010a1DEc6c46371A28A071F8bb01'
const SFPM = '0x0000000000000DEdEDdD16227aA3D836C5753194'
const startBlocks = {
ethereum: 21389983,
}

const Q96 = BigInt(2) ** BigInt(96)

function chainTvl(chain) {
return async (api) => {
const START_BLOCK = startBlocks[chain]
const poolDeployedLogs = (
await getLogs({
api,
target: FACTORY,
fromBlock: START_BLOCK,
topic: 'PoolDeployed(address,address,address,address)',
})
)

// can contain SFPM pools that don't have options markets yet
const poolInitializedLogs = (
await getLogs({
api,
target: SFPM,
fromBlock: START_BLOCK,
topic: 'PoolInitialized(address,uint64,int24,int24)',
})
)

const block = api.block

const poolData = {}

for (let log of poolDeployedLogs)
poolData[`0x${log.topics[2].substr(-40)}`.toLowerCase()] = {marketAddress: `0x${log.topics[1].substr(-40)}`.toLowerCase()}


for (let log of poolInitializedLogs) {
const V3PoolAddress = `0x${log.topics[1].substr(-40)}`.toLowerCase()
if (!poolData?.[V3PoolAddress]) poolData[V3PoolAddress] = {marketAddress: '0x3327b4D450fbB3a4b780510489C259D85776D559'.toLowerCase()} // random address
}

const token0Calls = Object.keys(poolData).map((V3Pool) => ({ target: V3Pool }))
const token1Calls = Object.keys(poolData).map((V3Pool) => ({ target: V3Pool }))

const token0Results = await sdk.api.abi.multiCall({
abi: "function token0() view returns (address)",
calls: token0Calls,
block,
chain,
})

token0Results.output.forEach((call, i) => {
poolData[call.input.target].token0 = call.output
})

const token1Results = await sdk.api.abi.multiCall({
abi: "function token1() view returns (address)",
calls: token1Calls,
block,
chain,
})

token1Results.output.forEach((call, i) => {
poolData[call.input.target].token1 = call.output
})

// Create balance calls for both tokens of each market
const balanceCalls = []

const LiquidityOwnedTokens = []

// Iterate through markets array directly
for (let [V3Pool, poolDataEntry] of Object.entries(poolData)) {
const mintLogs = (
await getLogs({
api,
target: V3Pool,
fromBlock: START_BLOCK,
extraKey: "mintCache",
eventAbi: `event Mint(address sender,address indexed owner,int24 indexed tickLower,int24 indexed tickUpper,uint128 amount,uint256 amount0,uint256 amount1)`,
topics: ["0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde","0x"+"000000000000000000000000"+SFPM.slice(2)]
})
)
const burnLogs = (
await getLogs({
api,
target: V3Pool,
fromBlock: START_BLOCK,
extraKey: "burnCache",
eventAbi: `event Burn(address indexed owner,int24 indexed tickLower,int24 indexed tickUpper,uint128 amount,uint256 amount0,uint256 amount1)`,
topics: ["0x0c396cd989a39f4459b5fa1aed6a9a8dcdbc45908acfd67e028cd568da98982c","0x"+"000000000000000000000000"+SFPM.slice(2)]
})
)

let sfpmOwnedLiquiditiesForMarket = {}

mintLogs.forEach((log) => {
const key = log.args.tickLower.toString()+"-"+log.args.tickUpper.toString()
sfpmOwnedLiquiditiesForMarket[key] = (sfpmOwnedLiquiditiesForMarket?.[key] ?? 0n) + log.args.amount})
burnLogs.forEach((log) => sfpmOwnedLiquiditiesForMarket[log.args.tickLower.toString()+"-"+log.args.tickUpper.toString()] -= log.args.amount)

const sqrtPriceX96 = (
await sdk.api.abi.call({
target: V3Pool,
abi: "function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)",
block,
chain,
})
).output.sqrtPriceX96

let extraTokens0 = 0n
let extraTokens1 = 0n

for (let [key, liquidity] of Object.entries(sfpmOwnedLiquiditiesForMarket)) {
const sqrtLower = BigInt(getSqrtRatioAtTick(Number(key.split("-")[0])).toString())
const sqrtUpper = BigInt(getSqrtRatioAtTick(Number(key.split("-")[1])).toString())

const [amount0, amount1] = getAmountsForLiquidity(BigInt(sqrtPriceX96), liquidity, sqrtLower, sqrtUpper)
extraTokens0 += amount0
extraTokens1 += amount1
}

LiquidityOwnedTokens.push(extraTokens0)
LiquidityOwnedTokens.push(extraTokens1)

balanceCalls.push({
target: poolDataEntry.token0,
params: poolDataEntry.marketAddress,
})
balanceCalls.push({
target: poolDataEntry.token1,
params: poolDataEntry.marketAddress,
})
}

const tokenBalances = await sdk.api.abi.multiCall({
abi: 'erc20:balanceOf',
calls: balanceCalls,
block,
chain,
})

// add tokens held in liquidity positions to TVL
tokenBalances.output = tokenBalances.output.map((call, i) => ({...call, output: (BigInt(call.output) + LiquidityOwnedTokens[i]).toString()}))

let transform = id => id
let balances = {}

sdk.util.sumMultiBalanceOf(balances, tokenBalances, true, transform)

return balances
}
}

function getAmount0ForLiquidity(
sqrtRatioAX96,
sqrtRatioBX96,
liquidity,
) {
if (sqrtRatioAX96 > sqrtRatioBX96) {
const temp = sqrtRatioAX96
sqrtRatioAX96 = sqrtRatioBX96
sqrtRatioBX96 = temp
}

return (liquidity * Q96 * (sqrtRatioBX96 - sqrtRatioAX96)) / sqrtRatioBX96 / sqrtRatioAX96
}

function getAmount1ForLiquidity(
sqrtRatioAX96,
sqrtRatioBX96,
liquidity,
) {
if (sqrtRatioAX96 > sqrtRatioBX96) {
const temp = sqrtRatioAX96
sqrtRatioAX96 = sqrtRatioBX96
sqrtRatioBX96 = temp
}

return (liquidity * (sqrtRatioBX96 - sqrtRatioAX96)) / Q96
}

function getAmountsForLiquidity(
priceX96,
liquidity,
sqrtRatioAX96,
sqrtRatioBX96,
) {
if (priceX96 <= sqrtRatioAX96) {
return [getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity), BigInt(0)]
} else if (priceX96 >= sqrtRatioBX96) {
return [BigInt(0), getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity)]
} else {
return [
getAmount0ForLiquidity(priceX96, sqrtRatioBX96, liquidity),
getAmount1ForLiquidity(sqrtRatioAX96, priceX96, liquidity),
]
}
}

function getSqrtRatioAtTick(tick) {
const MaxUint256 = JSBI.BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
const Q32 = JSBI.BigInt('0x100000000');
const ZERO = JSBI.BigInt(0);
const ONE = JSBI.BigInt(1);
var absTick = tick < 0 ? tick * -1 : tick;
var ratio = (absTick & 0x1) !== 0 ? JSBI.BigInt('0xfffcb933bd6fad37aa2d162d1a594001') : JSBI.BigInt('0x100000000000000000000000000000000');
if ((absTick & 0x2) !== 0) ratio = mulShift(ratio, '0xfff97272373d413259a46990580e213a');
if ((absTick & 0x4) !== 0) ratio = mulShift(ratio, '0xfff2e50f5f656932ef12357cf3c7fdcc');
if ((absTick & 0x8) !== 0) ratio = mulShift(ratio, '0xffe5caca7e10e4e61c3624eaa0941cd0');
if ((absTick & 0x10) !== 0) ratio = mulShift(ratio, '0xffcb9843d60f6159c9db58835c926644');
if ((absTick & 0x20) !== 0) ratio = mulShift(ratio, '0xff973b41fa98c081472e6896dfb254c0');
if ((absTick & 0x40) !== 0) ratio = mulShift(ratio, '0xff2ea16466c96a3843ec78b326b52861');
if ((absTick & 0x80) !== 0) ratio = mulShift(ratio, '0xfe5dee046a99a2a811c461f1969c3053');
if ((absTick & 0x100) !== 0) ratio = mulShift(ratio, '0xfcbe86c7900a88aedcffc83b479aa3a4');
if ((absTick & 0x200) !== 0) ratio = mulShift(ratio, '0xf987a7253ac413176f2b074cf7815e54');
if ((absTick & 0x400) !== 0) ratio = mulShift(ratio, '0xf3392b0822b70005940c7a398e4b70f3');
if ((absTick & 0x800) !== 0) ratio = mulShift(ratio, '0xe7159475a2c29b7443b29c7fa6e889d9');
if ((absTick & 0x1000) !== 0) ratio = mulShift(ratio, '0xd097f3bdfd2022b8845ad8f792aa5825');
if ((absTick & 0x2000) !== 0) ratio = mulShift(ratio, '0xa9f746462d870fdf8a65dc1f90e061e5');
if ((absTick & 0x4000) !== 0) ratio = mulShift(ratio, '0x70d869a156d2a1b890bb3df62baf32f7');
if ((absTick & 0x8000) !== 0) ratio = mulShift(ratio, '0x31be135f97d08fd981231505542fcfa6');
if ((absTick & 0x10000) !== 0) ratio = mulShift(ratio, '0x9aa508b5b7a84e1c677de54f3e99bc9');
if ((absTick & 0x20000) !== 0) ratio = mulShift(ratio, '0x5d6af8dedb81196699c329225ee604');
if ((absTick & 0x40000) !== 0) ratio = mulShift(ratio, '0x2216e584f5fa1ea926041bedfe98');
if ((absTick & 0x80000) !== 0) ratio = mulShift(ratio, '0x48a170391f7dc42444e8fa2');
if (tick > 0) ratio = JSBI.divide(MaxUint256, ratio);
// back to Q96
return JSBI.greaterThan(JSBI.remainder(ratio, Q32), ZERO) ? JSBI.add(JSBI.divide(ratio, Q32), ONE) : JSBI.divide(ratio, Q32);
}

function mulShift(val, mulBy) {
return JSBI.signedRightShift(JSBI.multiply(val, JSBI.BigInt(mulBy)), JSBI.BigInt(128));
}

module.exports = {
ethereum: {
tvl: chainTvl('ethereum'),
methodology: 'This adapter counts tokens held by all PanopticPool contracts created by the PanopticFactory, as well as the token composition of all Uniswap liquidity held by the SemiFungiblePositionManager (which is used by every PanopticPool to manage liquidity).',
start: 1734049391,
},
}

0 comments on commit 9cd2e6a

Please sign in to comment.