From 208b0f7f4ff1ba6fae59ce237fc0fc7aec7d7793 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Tue, 13 Jul 2021 12:36:10 +0530 Subject: [PATCH] Handle Pool burn event. (#134) Co-authored-by: nabarun --- packages/uni-info-watcher/src/database.ts | 32 +++++ packages/uni-info-watcher/src/entity/Burn.ts | 56 ++++++++ packages/uni-info-watcher/src/events.ts | 138 +++++++++++++++++++ packages/uni-watcher/src/client.ts | 9 ++ 4 files changed, 235 insertions(+) create mode 100644 packages/uni-info-watcher/src/entity/Burn.ts diff --git a/packages/uni-info-watcher/src/database.ts b/packages/uni-info-watcher/src/database.ts index 0f2c1b51..40054f4c 100644 --- a/packages/uni-info-watcher/src/database.ts +++ b/packages/uni-info-watcher/src/database.ts @@ -16,6 +16,7 @@ import { UniswapDayData } from './entity/UniswapDayData'; import { Tick } from './entity/Tick'; import { TokenDayData } from './entity/TokenDayData'; import { TokenHourData } from './entity/TokenHourData'; +import { Burn } from './entity/Burn'; export class Database { _config: ConnectionOptions @@ -298,6 +299,29 @@ export class Database { }); } + async loadBurn ({ id, blockNumber, ...values }:DeepPartial): Promise { + return this._conn.transaction(async (tx) => { + const repo = tx.getRepository(Burn); + + let selectQueryBuilder = repo.createQueryBuilder('burn') + .where('id = :id', { id }); + + if (blockNumber) { + selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber }); + } + + let entity = await selectQueryBuilder.orderBy('block_number', 'DESC') + .getOne(); + + if (!entity) { + entity = repo.create({ blockNumber, id, ...values }); + entity = await repo.save(entity); + } + + return entity; + }); + } + async loadTick ({ id, blockNumber, ...values }: DeepPartial): Promise { return this._conn.transaction(async (tx) => { const repo = tx.getRepository(Tick); @@ -470,6 +494,14 @@ export class Database { }); } + async saveTick (tick: Tick, blockNumber: number): Promise { + return this._conn.transaction(async (tx) => { + const repo = tx.getRepository(Tick); + tick.blockNumber = blockNumber; + return repo.save(tick); + }); + } + // Returns true if events have already been synced for the (block, token) combination. async didSyncEvents ({ blockHash, token }: { blockHash: string, token: string }): Promise { const numRows = await this._conn.getRepository(EventSyncProgress) diff --git a/packages/uni-info-watcher/src/entity/Burn.ts b/packages/uni-info-watcher/src/entity/Burn.ts new file mode 100644 index 00000000..f89a28fd --- /dev/null +++ b/packages/uni-info-watcher/src/entity/Burn.ts @@ -0,0 +1,56 @@ +import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; +import Decimal from 'decimal.js'; +import { decimalTransformer } from '@vulcanize/util'; + +import { Transaction } from './Transaction'; +import { Pool } from './Pool'; +import { Token } from './Token'; + +@Entity() +export class Burn { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('integer') + blockNumber!: number; + + @ManyToOne(() => Transaction, transaction => transaction.mints) + transaction!: Transaction + + @Column('bigint') + timestamp!: BigInt; + + @ManyToOne(() => Pool) + pool!: Pool + + @ManyToOne(() => Token) + token0!: Token + + @ManyToOne(() => Token) + token1!: Token + + @Column('varchar', { length: 42 }) + owner!: string + + // TODO: Assign origin with Transaction from address. + // @Column('varchar', { length: 42 }) + // origin!: string + + @Column('bigint') + amount!: bigint + + @Column('numeric', { transformer: decimalTransformer }) + amount0!: Decimal + + @Column('numeric', { transformer: decimalTransformer }) + amount1!: Decimal + + @Column('numeric', { transformer: decimalTransformer }) + amountUSD!: Decimal + + @Column('bigint') + tickLower!: bigint + + @Column('bigint') + tickUpper!: bigint +} diff --git a/packages/uni-info-watcher/src/events.ts b/packages/uni-info-watcher/src/events.ts index 4018a410..15b30fb1 100644 --- a/packages/uni-info-watcher/src/events.ts +++ b/packages/uni-info-watcher/src/events.ts @@ -36,6 +36,15 @@ interface MintEvent { amount1: bigint; } +interface BurnEvent { + owner: string; + tickLower: bigint; + tickUpper: bigint; + amount: bigint; + amount0: bigint; + amount1: bigint; +} + interface ResultEvent { proof: { data: string @@ -93,6 +102,11 @@ export class EventWatcher { this._handleMint(blockHash, blockNumber, contract, txHash, eventValues as MintEvent); break; + case 'BurnEvent': + log('Pool Burn event', contract); + this._handleBurn(blockHash, blockNumber, contract, txHash, eventValues as BurnEvent); + break; + default: break; } @@ -324,6 +338,130 @@ export class EventWatcher { await this._db.savePool(pool, blockNumber); await this._db.saveFactory(factory, blockNumber); + await Promise.all([ + await this._db.saveTick(lowerTick, blockNumber), + await this._db.saveTick(upperTick, blockNumber) + ]); + // Skipping update inner tick vars and tick day data as they are not queried. } + + async _handleBurn (blockHash: string, blockNumber: number, contractAddress: string, txHash: string, burnEvent: BurnEvent): Promise { + const bundle = await this._db.loadBundle({ id: '1', blockNumber }); + const poolAddress = contractAddress; + const pool = await this._db.loadPool({ id: poolAddress, blockNumber }); + + // TODO: In subgraph factory is fetched by hardcoded factory address. + // Currently fetching first factory in database as only one exists. + const [factory] = await this._db.getFactories({ blockNumber }, { limit: 1 }); + + const token0 = pool.token0; + const token1 = pool.token1; + const amount0 = convertTokenToDecimal(burnEvent.amount0, token0.decimals); + const amount1 = convertTokenToDecimal(burnEvent.amount1, token1.decimals); + + const amountUSD = amount0 + .times(token0.derivedETH.times(bundle.ethPriceUSD)) + .plus(amount1.times(token1.derivedETH.times(bundle.ethPriceUSD))); + + // Reset tvl aggregates until new amounts calculated. + factory.totalValueLockedETH = factory.totalValueLockedETH.minus(pool.totalValueLockedETH); + + // Update globals. + factory.txCount = factory.txCount + BigInt(1); + + // Update token0 data. + token0.txCount = token0.txCount + BigInt(1); + token0.totalValueLocked = token0.totalValueLocked.minus(amount0); + token0.totalValueLockedUSD = token0.totalValueLocked.times(token0.derivedETH.times(bundle.ethPriceUSD)); + + // Update token1 data. + token1.txCount = token1.txCount + BigInt(1); + token1.totalValueLocked = token1.totalValueLocked.minus(amount1); + token1.totalValueLockedUSD = token1.totalValueLocked.times(token1.derivedETH.times(bundle.ethPriceUSD)); + + // Pool data. + pool.txCount = pool.txCount + BigInt(1); + + // Pools liquidity tracks the currently active liquidity given pools current tick. + // We only want to update it on burn if the position being burnt includes the current tick. + if ( + pool.tick !== null && + burnEvent.tickLower <= pool.tick && + burnEvent.tickUpper > pool.tick + ) { + pool.liquidity = pool.liquidity - burnEvent.amount; + } + + pool.totalValueLockedToken0 = pool.totalValueLockedToken0.minus(amount0); + pool.totalValueLockedToken1 = pool.totalValueLockedToken1.minus(amount1); + + pool.totalValueLockedETH = pool.totalValueLockedToken0 + .times(token0.derivedETH) + .plus(pool.totalValueLockedToken1.times(token1.derivedETH)); + + pool.totalValueLockedUSD = pool.totalValueLockedETH.times(bundle.ethPriceUSD); + + // Reset aggregates with new amounts. + factory.totalValueLockedETH = factory.totalValueLockedETH.plus(pool.totalValueLockedETH); + factory.totalValueLockedUSD = factory.totalValueLockedETH.times(bundle.ethPriceUSD); + + // Burn entity. + const transaction = await loadTransaction(this._db, { txHash, blockNumber }); + + await this._db.loadBurn({ + id: transaction.id + '#' + pool.txCount.toString(), + blockNumber, + transaction, + timestamp: transaction.timestamp, + pool, + token0: pool.token0, + token1: pool.token1, + owner: burnEvent.owner, + + // TODO: Assign origin with Transaction from address. + // origin: event.transaction.from + + amount: burnEvent.amount, + amount0, + amount1, + amountUSD, + tickLower: burnEvent.tickLower, + tickUpper: burnEvent.tickUpper + }); + + // Tick entities. + const lowerTickId = poolAddress + '#' + (burnEvent.tickLower).toString(); + const upperTickId = poolAddress + '#' + (burnEvent.tickUpper).toString(); + const lowerTick = await this._db.loadTick({ id: lowerTickId, blockNumber }); + const upperTick = await this._db.loadTick({ id: upperTickId, blockNumber }); + const amount = burnEvent.amount; + lowerTick.liquidityGross = lowerTick.liquidityGross - amount; + lowerTick.liquidityNet = lowerTick.liquidityNet - amount; + upperTick.liquidityGross = upperTick.liquidityGross - amount; + upperTick.liquidityNet = upperTick.liquidityNet + amount; + + await updateUniswapDayData(this._db, { blockNumber, contractAddress }); + await updatePoolDayData(this._db, { blockNumber, contractAddress }); + await updatePoolHourData(this._db, { blockNumber, contractAddress }); + await updateTokenDayData(this._db, token0, { blockNumber }); + await updateTokenDayData(this._db, token0, { blockNumber }); + await updateTokenHourData(this._db, token0, { blockNumber }); + await updateTokenHourData(this._db, token0, { blockNumber }); + + // Skipping update Tick fee and Tick day data as they are not queried. + + await Promise.all([ + await this._db.saveTick(lowerTick, blockNumber), + await this._db.saveTick(upperTick, blockNumber) + ]); + + await Promise.all([ + this._db.saveToken(token0, blockNumber), + this._db.saveToken(token1, blockNumber) + ]); + + await this._db.savePool(pool, blockNumber); + await this._db.saveFactory(factory, blockNumber); + } } diff --git a/packages/uni-watcher/src/client.ts b/packages/uni-watcher/src/client.ts index 8728c0d8..87075cfb 100644 --- a/packages/uni-watcher/src/client.ts +++ b/packages/uni-watcher/src/client.ts @@ -49,6 +49,15 @@ export class Client { amount0 amount1 } + + ... on BurnEvent { + owner + tickLower + tickUpper + amount + amount0 + amount1 + } } } }