Skip to content

Commit

Permalink
add unit tests for initialize
Browse files Browse the repository at this point in the history
  • Loading branch information
mzywang committed May 29, 2024
1 parent b22849c commit 6097de2
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 83 deletions.
28 changes: 23 additions & 5 deletions src/mappings/pool/initialize.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
import { BigInt } from '@graphprotocol/graph-ts'
import { BigDecimal, BigInt } from '@graphprotocol/graph-ts'

import { Bundle, Pool, Token } from '../../types/schema'
import { Initialize } from '../../types/templates/Pool/Pool'
import { updatePoolDayData, updatePoolHourData } from '../../utils/intervalUpdates'
import { findEthPerToken, getEthPriceInUSD } from '../../utils/pricing'
import {
MINIMUM_ETH_LOCKED,
STABLE_COINS,
USDC_WETH_03_POOL,
WETH_ADDRESS,
findEthPerToken,
getEthPriceInUSD,
} from '../../utils/pricing'

export function handleInitialize(event: Initialize): void {
handleInitializeHelper(event)
}

export function handleInitializeHelper(
event: Initialize,
stablecoinWrappedNativePoolAddress: string = USDC_WETH_03_POOL,
stablecoinIsToken0: boolean = true, // true is stablecoin is token0, false if stablecoin is token1
wrappedNativeAddress: string = WETH_ADDRESS,
stablecoinAddresses: string[] = STABLE_COINS,
minimumEthLocked: BigDecimal = MINIMUM_ETH_LOCKED,
): void {
// update pool sqrt price and tick
const pool = Pool.load(event.address.toHexString())!
pool.sqrtPrice = event.params.sqrtPriceX96
Expand All @@ -18,16 +36,16 @@ export function handleInitialize(event: Initialize): void {

// update ETH price now that prices could have changed
const bundle = Bundle.load('1')!
bundle.ethPriceUSD = getEthPriceInUSD()
bundle.ethPriceUSD = getEthPriceInUSD(stablecoinWrappedNativePoolAddress, stablecoinIsToken0)
bundle.save()

updatePoolDayData(event)
updatePoolHourData(event)

// update token prices
if (token0 && token1) {
token0.derivedETH = findEthPerToken(token0 as Token)
token1.derivedETH = findEthPerToken(token1 as Token)
token0.derivedETH = findEthPerToken(token0 as Token, wrappedNativeAddress, stablecoinAddresses, minimumEthLocked)
token1.derivedETH = findEthPerToken(token1 as Token, wrappedNativeAddress, stablecoinAddresses, minimumEthLocked)
token0.save()
token1.save()
}
Expand Down
40 changes: 25 additions & 15 deletions src/utils/pricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { exponentToBigDecimal, safeDiv } from '../utils/index'
import { Bundle, Pool, Token } from './../types/schema'
import { ONE_BD, ZERO_BD, ZERO_BI } from './constants'

const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
const USDC_WETH_03_POOL = '0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8'
export const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
export const USDC_WETH_03_POOL = '0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8'

// token where amounts should contribute to tracked volume and liquidity
// usually tokens that many tokens are paired with s
Expand Down Expand Up @@ -33,7 +33,7 @@ export const WHITELIST_TOKENS: string[] = [
'0xfe2e637202056d30016725477c5da089ab0a043a', // sETH2
]

const STABLE_COINS: string[] = [
export const STABLE_COINS: string[] = [
'0x6b175474e89094c44da98b954eedeac495271d0f',
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
'0xdac17f958d2ee523a2206206994597c13d831ec7',
Expand All @@ -42,23 +42,28 @@ const STABLE_COINS: string[] = [
'0x4dd28568d05f09b02220b09c2cb307bfd837cb95',
]

const MINIMUM_ETH_LOCKED = BigDecimal.fromString('60')
export const MINIMUM_ETH_LOCKED = BigDecimal.fromString('60')

const Q192 = BigInt.fromI32(2).pow(192 as u8)
export function sqrtPriceX96ToTokenPrices(sqrtPriceX96: BigInt, token0: Token, token1: Token): BigDecimal[] {
const num = sqrtPriceX96.times(sqrtPriceX96).toBigDecimal()
const denom = BigDecimal.fromString(Q192.toString())
const price1 = num.div(denom).times(exponentToBigDecimal(token0.decimals)).div(exponentToBigDecimal(token1.decimals))
const price1 = num
.div(denom)
.times(exponentToBigDecimal(token0.decimals))
.div(exponentToBigDecimal(token1.decimals))

const price0 = safeDiv(BigDecimal.fromString('1'), price1)
return [price0, price1]
}

export function getEthPriceInUSD(): BigDecimal {
// fetch eth prices for each stablecoin
const usdcPool = Pool.load(USDC_WETH_03_POOL) // dai is token0
if (usdcPool !== null) {
return usdcPool.token0Price
export function getEthPriceInUSD(
stablecoinWrappedNativePoolAddress: string = USDC_WETH_03_POOL,
stablecoinIsToken0: boolean = true, // true is stablecoin is token0, false if stablecoin is token1
): BigDecimal {
const stablecoinWrappedNativePool = Pool.load(stablecoinWrappedNativePoolAddress)
if (stablecoinWrappedNativePool !== null) {
return stablecoinIsToken0 ? stablecoinWrappedNativePool.token0Price : stablecoinWrappedNativePool.token1Price
} else {
return ZERO_BD
}
Expand All @@ -68,8 +73,13 @@ export function getEthPriceInUSD(): BigDecimal {
* Search through graph to find derived Eth per token.
* @todo update to be derived ETH (add stablecoin estimates)
**/
export function findEthPerToken(token: Token): BigDecimal {
if (token.id == WETH_ADDRESS) {
export function findEthPerToken(
token: Token,
wrappedNativeAddress: string = WETH_ADDRESS,
stablecoinAddresses: string[] = STABLE_COINS,
minimumEthLocked: BigDecimal = MINIMUM_ETH_LOCKED,
): BigDecimal {
if (token.id == wrappedNativeAddress) {
return ONE_BD
}
const whiteList = token.whitelistPools
Expand All @@ -81,7 +91,7 @@ export function findEthPerToken(token: Token): BigDecimal {

// hardcoded fix for incorrect rates
// if whitelist includes token - get the safe price
if (STABLE_COINS.includes(token.id)) {
if (stablecoinAddresses.includes(token.id)) {
priceSoFar = safeDiv(ONE_BD, bundle.ethPriceUSD)
} else {
for (let i = 0; i < whiteList.length; ++i) {
Expand All @@ -96,7 +106,7 @@ export function findEthPerToken(token: Token): BigDecimal {
// get the derived ETH in pool
if (token1) {
const ethLocked = pool.totalValueLockedToken1.times(token1.derivedETH)
if (ethLocked.gt(largestLiquidityETH) && ethLocked.gt(MINIMUM_ETH_LOCKED)) {
if (ethLocked.gt(largestLiquidityETH) && ethLocked.gt(minimumEthLocked)) {
largestLiquidityETH = ethLocked
// token1 per our token * Eth per token1
priceSoFar = pool.token1Price.times(token1.derivedETH as BigDecimal)
Expand All @@ -108,7 +118,7 @@ export function findEthPerToken(token: Token): BigDecimal {
// get the derived ETH in pool
if (token0) {
const ethLocked = pool.totalValueLockedToken0.times(token0.derivedETH)
if (ethLocked.gt(largestLiquidityETH) && ethLocked.gt(MINIMUM_ETH_LOCKED)) {
if (ethLocked.gt(largestLiquidityETH) && ethLocked.gt(minimumEthLocked)) {
largestLiquidityETH = ethLocked
// token0 per our token * ETH per token0
priceSoFar = pool.token0Price.times(token0.derivedETH as BigDecimal)
Expand Down
77 changes: 76 additions & 1 deletion tests/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import { assert, createMockedFunction, newMockEvent } from 'matchstick-as'

import { handlePoolCreatedHelper } from '../src/mappings/factory'
import { PoolCreated } from '../src/types/Factory/Factory'
import { Pool, Token } from '../src/types/schema'
import { ZERO_BD, ZERO_BI } from '../src/utils/constants'

const USDC_MAINNET_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
const WETH_MAINNET_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
const WBTC_MAINNET_ADDRESS = '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599'
export const USDC_WETH_03_MAINNET_POOL = '0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8'
export const WBTC_WETH_03_MAINNET_POOL = '0xcbcdf9626bc03e24f779434178a73a0b4bad62ed'
export const POOL_FEE_TIER_03 = 3000
export const POOL_TICK_SPACING_03 = 60

Expand Down Expand Up @@ -34,13 +38,21 @@ export const WETH_MAINNET_FIXTURE: TokenFixture = {
decimals: '18',
}

export const WBTC_MAINNET_FIXTURE: TokenFixture = {
address: WBTC_MAINNET_ADDRESS,
symbol: 'WBTC',
name: 'Wrapped Bitcoin',
totalSupply: '200',
decimals: '8',
}

export const TEST_ETH_PRICE_USD = BigDecimal.fromString('2000')
export const TEST_USDC_DERIVED_ETH = BigDecimal.fromString('1').div(BigDecimal.fromString('2000'))
export const TEST_WETH_DERIVED_ETH = BigDecimal.fromString('1')

export const MOCK_EVENT = newMockEvent()

export const createTestPool = (
export const invokePoolCreatedWithMockedEthCalls = (
mockEvent: ethereum.Event,
factoryAddress: string,
token0: TokenFixture,
Expand Down Expand Up @@ -91,6 +103,69 @@ export const createTestPool = (
handlePoolCreatedHelper(poolCreatedEvent, factoryAddress, [token0.address, token1.address])
}

// More lightweight than the method above which invokes handlePoolCreated. This
// method only creates the pool entity while the above method also creates the
// relevant token and factory entities.
export const createAndStoreTestPool = (
poolAddress: string,
token0Address: string,
token1Address: string,
feeTier: i32,
): Pool => {
const pool = new Pool(poolAddress)
pool.createdAtTimestamp = ZERO_BI
pool.createdAtBlockNumber = ZERO_BI
pool.token0 = token0Address
pool.token1 = token1Address
pool.feeTier = BigInt.fromI32(feeTier)
pool.liquidity = ZERO_BI
pool.sqrtPrice = ZERO_BI
pool.token0Price = ZERO_BD
pool.token1Price = ZERO_BD
pool.tick = ZERO_BI
pool.observationIndex = ZERO_BI
pool.volumeToken0 = ZERO_BD
pool.volumeToken1 = ZERO_BD
pool.volumeUSD = ZERO_BD
pool.untrackedVolumeUSD = ZERO_BD
pool.feesUSD = ZERO_BD
pool.txCount = ZERO_BI
pool.collectedFeesToken0 = ZERO_BD
pool.collectedFeesToken1 = ZERO_BD
pool.collectedFeesUSD = ZERO_BD
pool.totalValueLockedToken0 = ZERO_BD
pool.totalValueLockedToken1 = ZERO_BD
pool.totalValueLockedUSD = ZERO_BD
pool.totalValueLockedETH = ZERO_BD
pool.totalValueLockedUSDUntracked = ZERO_BD
pool.liquidityProviderCount = ZERO_BI

pool.save()
return pool
}

export const createAndStoreTestToken = (tokenFixture: TokenFixture): Token => {
const token = new Token(tokenFixture.address)
token.symbol = tokenFixture.symbol
token.name = tokenFixture.name
token.decimals = BigInt.fromString(tokenFixture.decimals)
token.totalSupply = BigInt.fromString(tokenFixture.totalSupply)
token.volume = ZERO_BD
token.volumeUSD = ZERO_BD
token.untrackedVolumeUSD = ZERO_BD
token.feesUSD = ZERO_BD
token.txCount = ZERO_BI
token.poolCount = ZERO_BI
token.totalValueLocked = ZERO_BD
token.totalValueLockedUSD = ZERO_BD
token.totalValueLockedUSDUntracked = ZERO_BD
token.derivedETH = ZERO_BD
token.whitelistPools = []

token.save()
return token
}

// Typescript for Subgraphs do not support Record types so we use a 2D string array to represent the object instead.
export const assertObjectMatches = (entityType: string, id: string, obj: string[][]): void => {
for (let i = 0; i < obj.length; i++) {
Expand Down
4 changes: 2 additions & 2 deletions tests/handleBurn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { convertTokenToDecimal, fastExponentiation, safeDiv } from '../src/utils
import { FACTORY_ADDRESS, ONE_BD, ZERO_BI } from '../src/utils/constants'
import {
assertObjectMatches,
createTestPool,
invokePoolCreatedWithMockedEthCalls,
MOCK_EVENT,
POOL_FEE_TIER_03,
POOL_TICK_SPACING_03,
Expand Down Expand Up @@ -59,7 +59,7 @@ const BURN_EVENT = new Burn(

describe('handleBurn', () => {
beforeAll(() => {
createTestPool(
invokePoolCreatedWithMockedEthCalls(
MOCK_EVENT,
FACTORY_ADDRESS,
USDC_MAINNET_FIXTURE,
Expand Down
14 changes: 10 additions & 4 deletions tests/handleCollect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { convertTokenToDecimal } from '../src/utils'
import { FACTORY_ADDRESS, ZERO_BD } from '../src/utils/constants'
import {
assertObjectMatches,
createTestPool,
invokePoolCreatedWithMockedEthCalls,
MOCK_EVENT,
POOL_FEE_TIER_03,
POOL_TICK_SPACING_03,
Expand Down Expand Up @@ -59,7 +59,7 @@ const COLLECT_EVENT = new Collect(

describe('handleMint', () => {
beforeAll(() => {
createTestPool(
invokePoolCreatedWithMockedEthCalls(
MOCK_EVENT,
FACTORY_ADDRESS,
USDC_MAINNET_FIXTURE,
Expand Down Expand Up @@ -123,7 +123,10 @@ describe('handleMint', () => {
['totalValueLocked', collectedAmountToken0.neg().toString()],
[
'totalValueLockedUSD',
collectedAmountToken0.times(TEST_USDC_DERIVED_ETH.times(TEST_ETH_PRICE_USD)).neg().toString(),
collectedAmountToken0
.times(TEST_USDC_DERIVED_ETH.times(TEST_ETH_PRICE_USD))
.neg()
.toString(),
],
])

Expand All @@ -132,7 +135,10 @@ describe('handleMint', () => {
['totalValueLocked', collectedAmountToken1.neg().toString()],
[
'totalValueLockedUSD',
collectedAmountToken1.times(TEST_WETH_DERIVED_ETH.times(TEST_ETH_PRICE_USD)).neg().toString(),
collectedAmountToken1
.times(TEST_WETH_DERIVED_ETH.times(TEST_ETH_PRICE_USD))
.neg()
.toString(),
],
])

Expand Down
Loading

0 comments on commit 6097de2

Please sign in to comment.