Skip to content

Commit

Permalink
add unit tests for swap (#231)
Browse files Browse the repository at this point in the history
  • Loading branch information
mzywang authored May 30, 2024
1 parent 74fbcea commit cb859f1
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 9 deletions.
3 changes: 2 additions & 1 deletion src/mappings/pool/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getEthPriceInUSD,
MINIMUM_ETH_LOCKED,
STABLE_COINS,
STABLECOIN_IS_TOKEN0,
USDC_WETH_03_POOL,
WETH_ADDRESS,
} from '../../utils/pricing'
Expand All @@ -19,7 +20,7 @@ export function handleInitialize(event: Initialize): void {
export function handleInitializeHelper(
event: Initialize,
stablecoinWrappedNativePoolAddress: string = USDC_WETH_03_POOL,
stablecoinIsToken0: boolean = true,
stablecoinIsToken0: boolean = STABLECOIN_IS_TOKEN0,
wrappedNativeAddress: string = WETH_ADDRESS,
stablecoinAddresses: string[] = STABLE_COINS,
minimumEthLocked: BigDecimal = MINIMUM_ETH_LOCKED,
Expand Down
41 changes: 34 additions & 7 deletions src/mappings/pool/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,32 @@ import {
updateTokenHourData,
updateUniswapDayData,
} from '../../utils/intervalUpdates'
import { findEthPerToken, getEthPriceInUSD, getTrackedAmountUSD, sqrtPriceX96ToTokenPrices } from '../../utils/pricing'
import {
findEthPerToken,
getEthPriceInUSD,
getTrackedAmountUSD,
MINIMUM_ETH_LOCKED,
sqrtPriceX96ToTokenPrices,
STABLE_COINS,
STABLECOIN_IS_TOKEN0,
USDC_WETH_03_POOL,
WETH_ADDRESS,
WHITELIST_TOKENS,
} from '../../utils/pricing'

export function handleSwap(event: SwapEvent): void {
handleSwapHelper(event)
}

export function handleSwapHelper(
event: SwapEvent,
stablecoinWrappedNativePoolAddress: string = USDC_WETH_03_POOL,
stablecoinIsToken0: boolean = STABLECOIN_IS_TOKEN0,
wrappedNativeAddress: string = WETH_ADDRESS,
stablecoinAddresses: string[] = STABLE_COINS,
minimumEthLocked: BigDecimal = MINIMUM_ETH_LOCKED,
whitelistTokens: string[] = WHITELIST_TOKENS,
): void {
const bundle = Bundle.load('1')!
const factory = Factory.load(FACTORY_ADDRESS)!
const pool = Pool.load(event.address.toHexString())!
Expand Down Expand Up @@ -47,9 +70,13 @@ export function handleSwap(event: SwapEvent): void {
const amount1USD = amount1ETH.times(bundle.ethPriceUSD)

// get amount that should be tracked only - div 2 because cant count both input and output as volume
const amountTotalUSDTracked = getTrackedAmountUSD(amount0Abs, token0 as Token, amount1Abs, token1 as Token).div(
BigDecimal.fromString('2'),
)
const amountTotalUSDTracked = getTrackedAmountUSD(
amount0Abs,
token0 as Token,
amount1Abs,
token1 as Token,
whitelistTokens,
).div(BigDecimal.fromString('2'))
const amountTotalETHTracked = safeDiv(amountTotalUSDTracked, bundle.ethPriceUSD)
const amountTotalUSDUntracked = amount0USD.plus(amount1USD).div(BigDecimal.fromString('2'))

Expand Down Expand Up @@ -106,10 +133,10 @@ export function handleSwap(event: SwapEvent): void {
pool.save()

// update USD pricing
bundle.ethPriceUSD = getEthPriceInUSD()
bundle.ethPriceUSD = getEthPriceInUSD(stablecoinWrappedNativePoolAddress, stablecoinIsToken0)
bundle.save()
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)

/**
* Things afffected by new USD rates
Expand Down
3 changes: 2 additions & 1 deletion src/utils/pricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ONE_BD, ZERO_BD, ZERO_BI } from './constants'

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

// token where amounts should contribute to tracked volume and liquidity
// usually tokens that many tokens are paired with s
Expand Down Expand Up @@ -56,7 +57,7 @@ export function sqrtPriceX96ToTokenPrices(sqrtPriceX96: BigInt, token0: Token, t

export function getEthPriceInUSD(
stablecoinWrappedNativePoolAddress: string = USDC_WETH_03_POOL,
stablecoinIsToken0: boolean = true, // true is stablecoin is token0, false if stablecoin is token1
stablecoinIsToken0: boolean = STABLECOIN_IS_TOKEN0, // true is stablecoin is token0, false if stablecoin is token1
): BigDecimal {
const stablecoinWrappedNativePool = Pool.load(stablecoinWrappedNativePoolAddress)
if (stablecoinWrappedNativePool !== null) {
Expand Down
260 changes: 260 additions & 0 deletions tests/handleSwap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
import { Address, BigDecimal, BigInt, ethereum } from '@graphprotocol/graph-ts'
import { beforeAll, describe, test } from 'matchstick-as'

import { handleSwapHelper } from '../src/mappings/pool/swap'
import { Bundle, Token } from '../src/types/schema'
import { Swap } from '../src/types/templates/Pool/Pool'
import { convertTokenToDecimal, safeDiv } from '../src/utils'
import { FACTORY_ADDRESS, ZERO_BD } from '../src/utils/constants'
import { findEthPerToken, getEthPriceInUSD, getTrackedAmountUSD, sqrtPriceX96ToTokenPrices } from '../src/utils/pricing'
import {
assertObjectMatches,
invokePoolCreatedWithMockedEthCalls,
MOCK_EVENT,
POOL_FEE_TIER_03,
POOL_TICK_SPACING_03,
TEST_ETH_PRICE_USD,
TEST_USDC_DERIVED_ETH,
TEST_WETH_DERIVED_ETH,
USDC_MAINNET_FIXTURE,
USDC_WETH_03_MAINNET_POOL,
WETH_MAINNET_FIXTURE,
} from './constants'

class SwapFixture {
sender: Address
recipient: Address
amount0: BigInt
amount1: BigInt
sqrtPriceX96: BigInt
liquidity: BigInt
tick: i32
}

// https://etherscan.io/tx/0xd6005a794596212a1bdc19178e04e18eb8e9e0963d7073303bcb47d6186e757e#eventlog
const SWAP_FIXTURE: SwapFixture = {
sender: Address.fromString('0x6F1cDbBb4d53d226CF4B917bF768B94acbAB6168'),
recipient: Address.fromString('0x6F1cDbBb4d53d226CF4B917bF768B94acbAB6168'),
amount0: BigInt.fromString('-77505140556'),
amount1: BigInt.fromString('20824112148200096620'),
sqrtPriceX96: BigInt.fromString('1296814378469562426931209291431936'),
liquidity: BigInt.fromString('8433670604946078834'),
tick: 194071,
}

const SWAP_EVENT = new Swap(
Address.fromString(USDC_WETH_03_MAINNET_POOL),
MOCK_EVENT.logIndex,
MOCK_EVENT.transactionLogIndex,
MOCK_EVENT.logType,
MOCK_EVENT.block,
MOCK_EVENT.transaction,
[
new ethereum.EventParam('sender', ethereum.Value.fromAddress(SWAP_FIXTURE.sender)),
new ethereum.EventParam('recipient', ethereum.Value.fromAddress(SWAP_FIXTURE.recipient)),
new ethereum.EventParam('amount0', ethereum.Value.fromUnsignedBigInt(SWAP_FIXTURE.amount0)),
new ethereum.EventParam('amount1', ethereum.Value.fromUnsignedBigInt(SWAP_FIXTURE.amount1)),
new ethereum.EventParam('sqrtPriceX96', ethereum.Value.fromUnsignedBigInt(SWAP_FIXTURE.sqrtPriceX96)),
new ethereum.EventParam('liquidity', ethereum.Value.fromUnsignedBigInt(SWAP_FIXTURE.liquidity)),
new ethereum.EventParam('tick', ethereum.Value.fromI32(SWAP_FIXTURE.tick)),
],
MOCK_EVENT.receipt,
)

describe('handleSwap', () => {
beforeAll(() => {
invokePoolCreatedWithMockedEthCalls(
MOCK_EVENT,
FACTORY_ADDRESS,
USDC_MAINNET_FIXTURE,
WETH_MAINNET_FIXTURE,
USDC_WETH_03_MAINNET_POOL,
POOL_FEE_TIER_03,
POOL_TICK_SPACING_03,
)

const bundle = new Bundle('1')
bundle.ethPriceUSD = TEST_ETH_PRICE_USD
bundle.save()

const usdcEntity = Token.load(USDC_MAINNET_FIXTURE.address)!
usdcEntity.derivedETH = TEST_USDC_DERIVED_ETH
usdcEntity.save()

const wethEntity = Token.load(WETH_MAINNET_FIXTURE.address)!
wethEntity.derivedETH = TEST_WETH_DERIVED_ETH
wethEntity.save()
})

test('success', () => {
const stablecoinWrappedNativePoolAddress = USDC_WETH_03_MAINNET_POOL
const stablecoinIsToken0 = true
const wrappedNativeAddress = WETH_MAINNET_FIXTURE.address
const stablecoinAddresses = [USDC_MAINNET_FIXTURE.address]
const minimumEthLocked = ZERO_BD
const whitelistTokens = [USDC_MAINNET_FIXTURE.address, WETH_MAINNET_FIXTURE.address]

const token0 = Token.load(USDC_MAINNET_FIXTURE.address)!
const token1 = Token.load(WETH_MAINNET_FIXTURE.address)!

const amount0 = convertTokenToDecimal(SWAP_FIXTURE.amount0, BigInt.fromString(USDC_MAINNET_FIXTURE.decimals))
const amount1 = convertTokenToDecimal(SWAP_FIXTURE.amount1, BigInt.fromString(WETH_MAINNET_FIXTURE.decimals))

const amount0Abs = amount0.lt(ZERO_BD) ? amount0.times(BigDecimal.fromString('-1')) : amount0
const amount1Abs = amount1.lt(ZERO_BD) ? amount1.times(BigDecimal.fromString('-1')) : amount1

// calculate this before calling handleSwapHelper because it updates the derivedETH of the tokens which will affect calculations
const amountTotalUSDTracked = getTrackedAmountUSD(amount0Abs, token0, amount1Abs, token1, whitelistTokens).div(
BigDecimal.fromString('2'),
)

const amount0ETH = amount0Abs.times(TEST_USDC_DERIVED_ETH)
const amount1ETH = amount1Abs.times(TEST_WETH_DERIVED_ETH)

const amount0USD = amount0ETH.times(TEST_ETH_PRICE_USD)
const amount1USD = amount1ETH.times(TEST_ETH_PRICE_USD)

const amountTotalETHTRacked = safeDiv(amountTotalUSDTracked, TEST_ETH_PRICE_USD)
const amountTotalUSDUntracked = amount0USD.plus(amount1USD).div(BigDecimal.fromString('2'))

const feeTierBD = BigDecimal.fromString(POOL_FEE_TIER_03.toString())
const feesETH = amountTotalETHTRacked.times(feeTierBD).div(BigDecimal.fromString('1000000'))
const feesUSD = amountTotalUSDTracked.times(feeTierBD).div(BigDecimal.fromString('1000000'))

handleSwapHelper(
SWAP_EVENT,
stablecoinWrappedNativePoolAddress,
stablecoinIsToken0,
wrappedNativeAddress,
stablecoinAddresses,
minimumEthLocked,
whitelistTokens,
)

const newEthPrice = getEthPriceInUSD(USDC_WETH_03_MAINNET_POOL, true)
const newPoolPrices = sqrtPriceX96ToTokenPrices(SWAP_FIXTURE.sqrtPriceX96, token0, token1)
const newToken0DerivedETH = findEthPerToken(token0, wrappedNativeAddress, stablecoinAddresses, minimumEthLocked)
const newToken1DerivedETH = findEthPerToken(token1, wrappedNativeAddress, stablecoinAddresses, minimumEthLocked)

const totalValueLockedETH = amount0.times(newToken0DerivedETH).plus(amount1.times(newToken1DerivedETH))

assertObjectMatches('Factory', FACTORY_ADDRESS, [
['txCount', '1'],
['totalVolumeETH', amountTotalETHTRacked.toString()],
['totalVolumeUSD', amountTotalUSDTracked.toString()],
['untrackedVolumeUSD', amountTotalUSDUntracked.toString()],
['totalFeesETH', feesETH.toString()],
['totalFeesUSD', feesUSD.toString()],
['totalValueLockedETH', totalValueLockedETH.toString()],
['totalValueLockedUSD', totalValueLockedETH.times(newEthPrice).toString()],
])

assertObjectMatches('Pool', USDC_WETH_03_MAINNET_POOL, [
['volumeToken0', amount0Abs.toString()],
['volumeToken1', amount1Abs.toString()],
['volumeUSD', amountTotalUSDTracked.toString()],
['untrackedVolumeUSD', amountTotalUSDUntracked.toString()],
['feesUSD', feesUSD.toString()],
['txCount', '1'],
['liquidity', SWAP_FIXTURE.liquidity.toString()],
['tick', SWAP_FIXTURE.tick.toString()],
['sqrtPrice', SWAP_FIXTURE.sqrtPriceX96.toString()],
['totalValueLockedToken0', amount0.toString()],
['totalValueLockedToken1', amount1.toString()],
['token0Price', newPoolPrices[0].toString()],
['token1Price', newPoolPrices[1].toString()],
['totalValueLockedETH', totalValueLockedETH.toString()],
['totalValueLockedUSD', totalValueLockedETH.times(newEthPrice).toString()],
])

assertObjectMatches('Token', USDC_MAINNET_FIXTURE.address, [
['volume', amount0Abs.toString()],
['totalValueLocked', amount0.toString()],
['volumeUSD', amountTotalUSDTracked.toString()],
['untrackedVolumeUSD', amountTotalUSDUntracked.toString()],
['feesUSD', feesUSD.toString()],
['txCount', '1'],
['derivedETH', newToken0DerivedETH.toString()],
['totalValueLockedUSD', amount0.times(newToken0DerivedETH).times(newEthPrice).toString()],
])

assertObjectMatches('Token', WETH_MAINNET_FIXTURE.address, [
['volume', amount1Abs.toString()],
['totalValueLocked', amount1.toString()],
['volumeUSD', amountTotalUSDTracked.toString()],
['untrackedVolumeUSD', amountTotalUSDUntracked.toString()],
['feesUSD', feesUSD.toString()],
['txCount', '1'],
['derivedETH', newToken1DerivedETH.toString()],
['totalValueLockedUSD', amount1.times(newToken1DerivedETH).times(newEthPrice).toString()],
])

assertObjectMatches('Swap', MOCK_EVENT.transaction.hash.toHexString() + '-' + MOCK_EVENT.logIndex.toString(), [
['transaction', MOCK_EVENT.transaction.hash.toHexString()],
['timestamp', MOCK_EVENT.block.timestamp.toString()],
['pool', USDC_WETH_03_MAINNET_POOL],
['token0', USDC_MAINNET_FIXTURE.address],
['token1', WETH_MAINNET_FIXTURE.address],
['sender', SWAP_FIXTURE.sender.toHexString()],
['origin', MOCK_EVENT.transaction.from.toHexString()],
['recipient', SWAP_FIXTURE.recipient.toHexString()],
['amount0', amount0.toString()],
['amount1', amount1.toString()],
['amountUSD', amountTotalUSDTracked.toString()],
['tick', SWAP_FIXTURE.tick.toString()],
['sqrtPriceX96', SWAP_FIXTURE.sqrtPriceX96.toString()],
['logIndex', MOCK_EVENT.logIndex.toString()],
])

const dayId = MOCK_EVENT.block.timestamp.toI32() / 86400
const hourId = MOCK_EVENT.block.timestamp.toI32() / 3600

assertObjectMatches('UniswapDayData', dayId.toString(), [
['volumeETH', amountTotalETHTRacked.toString()],
['volumeUSD', amountTotalUSDTracked.toString()],
['feesUSD', feesUSD.toString()],
])

assertObjectMatches('PoolDayData', USDC_WETH_03_MAINNET_POOL + '-' + dayId.toString(), [
['volumeUSD', amountTotalUSDTracked.toString()],
['volumeToken0', amount0Abs.toString()],
['volumeToken1', amount1Abs.toString()],
['feesUSD', feesUSD.toString()],
])

assertObjectMatches('PoolHourData', USDC_WETH_03_MAINNET_POOL + '-' + hourId.toString(), [
['volumeUSD', amountTotalUSDTracked.toString()],
['volumeToken0', amount0Abs.toString()],
['volumeToken1', amount1Abs.toString()],
['feesUSD', feesUSD.toString()],
])

assertObjectMatches('TokenDayData', USDC_MAINNET_FIXTURE.address + '-' + dayId.toString(), [
['volume', amount0Abs.toString()],
['volumeUSD', amountTotalUSDTracked.toString()],
['untrackedVolumeUSD', amountTotalUSDTracked.toString()],
['feesUSD', feesUSD.toString()],
])

assertObjectMatches('TokenDayData', WETH_MAINNET_FIXTURE.address + '-' + dayId.toString(), [
['volume', amount1Abs.toString()],
['volumeUSD', amountTotalUSDTracked.toString()],
['untrackedVolumeUSD', amountTotalUSDTracked.toString()],
['feesUSD', feesUSD.toString()],
])

assertObjectMatches('TokenHourData', USDC_MAINNET_FIXTURE.address + '-' + hourId.toString(), [
['volume', amount0Abs.toString()],
['volumeUSD', amountTotalUSDTracked.toString()],
['untrackedVolumeUSD', amountTotalUSDTracked.toString()],
['feesUSD', feesUSD.toString()],
])

assertObjectMatches('TokenHourData', WETH_MAINNET_FIXTURE.address + '-' + hourId.toString(), [
['volume', amount1Abs.toString()],
['volumeUSD', amountTotalUSDTracked.toString()],
['untrackedVolumeUSD', amountTotalUSDTracked.toString()],
['feesUSD', feesUSD.toString()],
])
})
})

0 comments on commit cb859f1

Please sign in to comment.