123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- const { ethers } = require("hardhat")
- const { expect } = require("chai")
- const { BigNumber } = require("ethers")
- const { CHAIN_ID_TO_NAME, POOL_ID_TO_NAME } = require("./constants")
- const util = require("util")
- printEndpoints = (endpoints) => {
- let printFriendly = JSON.parse(JSON.stringify(endpoints))
- for (const [endpointId, endpoint] of Object.entries(printFriendly)) {
- endpoint.factory = endpoint.factory.address
- endpoint.router = endpoint.router.address
- endpoint.bridge = endpoint.bridge.address
- endpoint.lzEndpoint = endpoint.lzEndpoint.address
- endpoint.feeLibrary = endpoint.feeLibrary.address
- endpoint.pools = Object.fromEntries(
- Object.entries(endpoint.pools).map(([poolId, value]) => {
- const chainPaths = {}
- for (const [chainId, _chainPaths] of Object.entries(value.chainPaths)) {
- chainPaths[CHAIN_ID_TO_NAME[chainId]] = Object.fromEntries(
- Object.entries(_chainPaths).map(([x, y]) => [POOL_ID_TO_NAME[x], y])
- )
- }
- delete value.dstChainWeights
- return [poolId, { ...value, token: value.token.address, pool: value.pool.address, chainPaths }]
- })
- )
- delete Object.assign(printFriendly, { [CHAIN_ID_TO_NAME[endpointId]]: printFriendly[endpointId] })[endpointId]
- }
- console.log(util.inspect(printFriendly, { showHidden: false, depth: null, colors: true }))
- }
- stringifyBigNumbers = (resp) => {
- if (resp instanceof Array) {
- return resp.map((x) => stringifyBigNumbers(x))
- } else if (BigNumber.isBigNumber(resp)) {
- return resp.toString()
- } else if (resp instanceof Object) {
- return Object.fromEntries(Object.entries(resp).map(([k, v]) => [k, stringifyBigNumbers(v)]))
- } else {
- return resp
- }
- }
- getPoolState = async (poolObj) => {
- return {
- actualLiquidity: await poolObj.token.balanceOf(poolObj.pool.address),
- totalLiquidity: await poolObj.pool.totalLiquidity(),
- totalSupply: await poolObj.pool.totalSupply(),
- protocolFeeBalance: await poolObj.pool.protocolFeeBalance(),
- eqFeePool: await poolObj.pool.eqFeePool(),
- mintFeeBalance: await poolObj.pool.mintFeeBalance(),
- deltaCredit: await poolObj.pool.deltaCredit(),
- convertRate: await poolObj.pool.convertRate(),
- }
- }
- getPoolStates = async (endpoints, poolIds) => {
- let resp = []
- for (const chainId of Object.keys(endpoints)) {
- let chain = endpoints[chainId]
- for (const poolId of poolIds) {
- let poolObj = chain.pools[poolId]
- resp.push(await getPoolState(poolObj))
- }
- }
- return resp
- }
- getTokenState = async (endpoints, tokenId) => {
- let resp = {}
- for (const endpoint of Object.values(endpoints)) {
- for (const [k, v] of Object.entries(await getPoolState(endpoint.pools[tokenId]))) {
- resp[k] = (resp[k] || BigNumber.from(0)).add(v)
- }
- }
- return resp
- }
- getPooledTokenState = async (endpoints, tokenIds) => {
- const resp = {}
- for (const tokenId of tokenIds) {
- for (const [k, v] of Object.entries(await getTokenState(endpoints, tokenId))) {
- resp[k] = (resp[k] || BigNumber.from(0)).add(v)
- }
- }
- return resp
- }
- // only pass token ids that are sharing pools of liquidity
- // for individual tokens, just pass a list of 1
- checkTokenState = async (endpoints, tokenIds) => {
- const tokenState = await getPooledTokenState(endpoints, tokenIds)
- const { actualLiquidity, totalLiquidity, protocolFeeBalance, eqFeePool, mintFeeBalance, deltaCredit } = tokenState
- const inferredLiquidity = totalLiquidity.add(protocolFeeBalance).add(eqFeePool).add(mintFeeBalance).add(deltaCredit)
- const diff = actualLiquidity.sub(inferredLiquidity)
- if (diff > 0) throw `Mismatched liquidity/fee balances -> ${diff}`
- return tokenState
- }
- getTokenBalances = async (listOfTokens, user) => {
- const resp = []
- for (const u of Array.isArray(user) ? user : [user]) {
- for (const [tokenName, token] of Object.entries(listOfTokens)) {
- const balance = await token.balanceOf(u.address)
- resp.push({ name: tokenName, balance })
- }
- }
- return resp
- }
- getTokenBalancesFromPools = async (listOfPoolObj, user) => {
- const listOfLpPools = Object.fromEntries(listOfPoolObj.map((poolObj) => [poolObj.name, poolObj.token]))
- return await getTokenBalances(listOfLpPools, user)
- }
- getLpBalancesFromPools = async (listOfPoolObj, user) => {
- const listOfTokens = Object.fromEntries(listOfPoolObj.map((poolObj) => [poolObj.name, poolObj.pool]))
- return await getTokenBalances(listOfTokens, user)
- }
- getChainPath = async (srcPoolObj, dstPoolObj) => {
- const cpIndex = await srcPoolObj.pool.chainPathIndexLookup(dstPoolObj.chainId, dstPoolObj.id)
- const deltaCredit = await srcPoolObj.pool.deltaCredit()
- let { dstChainId, dstPoolId, weight, balance, credits, lkb, idealBalance } = await srcPoolObj.pool.chainPaths(cpIndex)
- return { dstChainId, dstPoolId, weight, balance, credits, lkb, deltaCredit, idealBalance }
- }
- getChainPaths = async (srcPoolObj, dstPoolObj) => {
- return {
- srcChainPath: await getChainPath(srcPoolObj, dstPoolObj),
- dstChainPath: await getChainPath(dstPoolObj, srcPoolObj),
- }
- }
- getAllChainPaths = async (LisOfPoolObj) => {
- const resp = {}
- for (let a = 0; a < LisOfPoolObj.length; a++) {
- for (let b = 0; b < LisOfPoolObj.length; b++) {
- const srcPoolObj = LisOfPoolObj[a]
- const dstPoolObj = LisOfPoolObj[b]
- if (srcPoolObj.chainId === dstPoolObj.chainId) continue
- const chainPathName = `${srcPoolObj.name}->${dstPoolObj.name}`
- resp[chainPathName] = await getChainPath(srcPoolObj, dstPoolObj)
- }
- }
- return resp
- }
- printPoolStates = async (poolObjs) => {
- console.log("\nPool States: ")
- let totals = {}
- for (const poolObj of poolObjs) {
- const poolState = stringifyBigNumbers(await getPoolState(poolObj))
- for (const [k, v] of Object.entries(poolState)) {
- totals[k] = (totals[k] || BigNumber.from(0)).add(v)
- }
- console.log(
- `${poolObj.name} -> `,
- `actualLiquidity: ${poolState.actualLiquidity} `,
- `totalLiquidity: ${poolState.totalLiquidity} `,
- `totalSupply: ${poolState.totalSupply} `,
- `protocolFeeBalance: ${poolState.protocolFeeBalance} `,
- `eqFeePool: ${poolState.eqFeePool} `,
- `mintFeeBalance: ${poolState.mintFeeBalance} `,
- `deltaCredit: ${poolState.deltaCredit} `,
- `convertRate: ${poolState.convertRate}`
- )
- }
- console.log(
- ` TOTALS: `,
- `TotalActualLiquidity: ${totals.actualLiquidity} `,
- `TotalTotalLiquidity: ${totals.totalLiquidity} `,
- `TotalTotalSupply: ${totals.totalSupply} `,
- `TotalProtocolFeeBalance: ${totals.protocolFeeBalance} `,
- `TotalEqFeePool: ${totals.eqFeePool} `,
- `TotalMintFeeBalance: ${totals.mintFeeBalance} `,
- `TotalDeltaCredit: ${totals.deltaCredit} `
- )
- }
- printTokenStates = async (endpoints, tokenIds) => {
- console.log("\nToken States: ")
- for (const tokenId of tokenIds) {
- const tokenState = stringifyBigNumbers(await getTokenState(endpoints, tokenId))
- console.log(
- `actualLiquidity: ${tokenState.actualLiquidity} `,
- `totalLiquidity: ${tokenState.totalLiquidity} `,
- `totalSupply: ${tokenState.totalSupply} `,
- `deltaCredit: ${tokenState.deltaCredit} `
- )
- }
- }
- printPooledTokenStates = async (endpoints, tokenIds) => {
- console.log("\nPooled Token States:")
- const tokenState = stringifyBigNumbers(await getPooledTokenState(endpoints, tokenIds))
- console.log(
- `actualLiquidity: ${tokenState.actualLiquidity} `,
- `totalLiquidity: ${tokenState.totalLiquidity} `,
- `totalSupply: ${tokenState.totalSupply} `,
- `deltaCredit: ${tokenState.deltaCredit} `
- )
- }
- printChainPaths = async (listOfPoolObj) => {
- console.log("\nChain Paths: ")
- let totals = {}
- let a = await getAllChainPaths(listOfPoolObj)
- const chainPaths = stringifyBigNumbers(a)
- for (const [name, chainPath] of Object.entries(chainPaths)) {
- for (const [k, v] of Object.entries(chainPath)) {
- totals[k] = (totals[k] || BigNumber.from(0)).add(v)
- }
- console.log(
- `${name}: `,
- `balance: ${chainPath.balance} `,
- `credits: ${chainPath.credits} `,
- `lkb: ${chainPath.lkb} `,
- `weight: ${chainPath.weight}`,
- `ideal balance: ${chainPath.idealBalance}`
- )
- }
- console.log(
- ` TOTALS: `,
- `balance: ${totals.balance} `,
- `credits: ${totals.credits} `,
- `lkb: ${totals.lkb} `,
- `weight: ${totals.weight}`
- )
- }
- printLpBalancesFromPool = async (listOfPoolObj, users) => {
- console.log("\nLp Balances:")
- for (const user of users) {
- let a = await getLpBalancesFromPools(listOfPoolObj, user)
- const lpBalances = stringifyBigNumbers(a)
- for (const lpBalance of lpBalances) {
- console.log(`${user.name}: lp-${lpBalance.name} -> ${lpBalance.balance}`)
- }
- }
- }
- printTokenBalancesFromPool = async (listOfPoolObj, users) => {
- console.log("\nToken Balances: ")
- for (const user of users) {
- let a = await getTokenBalancesFromPools(listOfPoolObj, user)
- const tokenBalances = stringifyBigNumbers(a)
- for (const tokenBalance of tokenBalances) {
- console.log(`${user.name}: ${tokenBalance.name} -> ${tokenBalance.balance}`)
- }
- }
- }
- audit = async (endpoints, poolObjs) => {
- /*
- compute global metrics across all chains
- - globalBookedLiquidity = (pool.totalLiquidity) sum by chains
- - globalPromisedLiquidity = (unallocated liquidity (deltaCredits)
- + allocated liquidity (lkb + credits)) sum by chains
- */
- let globalBookedLiquidity = ethers.BigNumber.from(0)
- let globalPromisedLiquidity = ethers.BigNumber.from(0)
- // for each chain
- for (const endpoint of endpoints) {
- let { chainId: srcChainId } = endpoint
- // filter for pools that are on this endpoint
- const srcPoolObjs = poolObjs.filter((pool) => pool.chainId == srcChainId)
- for (const srcPoolObj of srcPoolObjs) {
- //loop over chain paths of pool
- const { pool: srcPool, token, chainPaths, id: srcPoolId } = srcPoolObj
- let totalQueryBalance = (await token.balanceOf(srcPool.address))
- .div(await srcPool.convertRate())
- .sub(await srcPool.eqFeePool())
- .sub(await srcPool.protocolFeeBalance())
- .sub(await srcPool.mintFeeBalance())
- let totalPromisedBalance = await srcPool.deltaCredit()
- globalBookedLiquidity = globalBookedLiquidity.add(await srcPool.totalLiquidity())
- // for each iterate by destination chain
- for (const [dstChainId, dstPools] of Object.entries(chainPaths)) {
- for (const dstPoolId of Object.keys(dstPools)) {
- const srcCP = await srcPool.chainPaths(await srcPool.chainPathIndexLookup(dstChainId, dstPoolId))
- const dstPoolObjs = poolObjs.filter((pool) => pool.chainId == dstChainId && pool.id == dstPoolId)
- for (const dstPoolObj of dstPoolObjs) {
- const dstCP = await dstPoolObj.pool.chainPaths(await dstPoolObj.pool.chainPathIndexLookup(srcChainId, srcPoolId))
- // if no msg inFlight, dstCp.lkb === srcCp.balance
- const transactionInbound = srcCP.lkb.sub(dstCP.balance)
- const promisedBalance = dstCP.balance.add(srcCP.credits)
- totalPromisedBalance = totalPromisedBalance.add(transactionInbound).add(promisedBalance)
- }
- }
- }
- if (totalPromisedBalance.toString() !== totalQueryBalance.toString()) {
- console.log("\n\n", "totalPromise: ", totalPromisedBalance.toString(), "totalQueryBalance: ", totalQueryBalance.toString())
- }
- /*
- ASSERT - IFG constraints for Pool to all its associated chainPaths
- */
- expect(totalPromisedBalance).to.equal(totalQueryBalance)
- globalPromisedLiquidity = globalPromisedLiquidity.add(totalPromisedBalance)
- }
- }
- // this can only be asserted globally, as any transaction would temporarily make the pool imbalanced
- expect(globalPromisedLiquidity).to.equal(globalBookedLiquidity)
- }
- module.exports = {
- getPoolState,
- getPoolStates,
- printEndpoints,
- getTokenBalancesFromPools,
- getLpBalancesFromPools,
- getChainPath,
- getChainPaths,
- getAllChainPaths,
- stringifyBigNumbers,
- printPoolStates,
- printPooledTokenStates,
- printTokenStates,
- getTokenState,
- printChainPaths,
- printLpBalancesFromPool,
- printTokenBalancesFromPool,
- audit,
- }
|