diff --git a/packages/uni-info-watcher/.mocharc.yml b/packages/uni-info-watcher/.mocharc.yml index 547437d9..e1238532 100644 --- a/packages/uni-info-watcher/.mocharc.yml +++ b/packages/uni-info-watcher/.mocharc.yml @@ -1,4 +1,4 @@ -timeout: '20000' +timeout: '50000' bail: true exit: true # TODO: Find out why the program doesn't exit on its own. require: 'ts-node/register' diff --git a/packages/uni-info-watcher/src/smoke.test.ts b/packages/uni-info-watcher/src/smoke.test.ts index 7e1454f6..73db4150 100644 --- a/packages/uni-info-watcher/src/smoke.test.ts +++ b/packages/uni-info-watcher/src/smoke.test.ts @@ -1,9 +1,22 @@ import { expect } from 'chai'; -import { ethers, Contract, Signer } from 'ethers'; +import { ethers, Contract, ContractTransaction, Signer } from 'ethers'; import { request } from 'graphql-request'; import 'mocha'; +import _ from 'lodash'; -import { Config, getConfig, wait, deployTokens, createPool, initializePool } from '@vulcanize/util'; +import { + Config, + getConfig, + wait, + deployTokens, + deployUniswapV3Callee, + TESTERC20_ABI, + createPool, + initializePool, + getMinTick, + getMaxTick, + approveToken +} from '@vulcanize/util'; import { Client as UniClient, watchEvent } from '@vulcanize/uni-watcher'; import { abi as FACTORY_ABI @@ -18,7 +31,9 @@ import { queryToken, queryPoolsByTokens, queryPoolById, - queryPoolDayData + queryPoolDayData, + queryMints, + queryTicks } from '../test/queries'; const NETWORK_RPC_URL = 'http://localhost:8545'; @@ -28,10 +43,13 @@ const TICK_MIN = -887272; describe('uni-info-watcher', () => { let factory: Contract; let pool: Contract; + let token0: Contract; + let token1: Contract; let token0Address: string; let token1Address: string; let signer: Signer; + let recipient: string; let config: Config; let endpoint: string; let uniClient: UniClient; @@ -39,6 +57,7 @@ describe('uni-info-watcher', () => { before(async () => { const provider = new ethers.providers.JsonRpcProvider(NETWORK_RPC_URL); signer = provider.getSigner(); + recipient = await signer.getAddress(); const configFile = './environments/local.toml'; config = await getConfig(configFile); @@ -94,7 +113,7 @@ describe('uni-info-watcher', () => { expect(data1.token).to.be.null; }); - it('should create pool', async () => { + it('should trigger PoolCreatedEvent', async () => { // Create Pool. createPool(factory, token0Address, token1Address, fee); @@ -103,7 +122,7 @@ describe('uni-info-watcher', () => { await watchEvent(uniClient, eventType); // Sleeping for 5 sec for the entities to be processed. - await wait(5000); + await wait(10000); }); it('should create Token entities', async () => { @@ -129,6 +148,12 @@ describe('uni-info-watcher', () => { expect(pool.address).to.not.be.empty; expect(data.pools[0].feeTier).to.be.equal(fee.toString()); + + // Initializing the token variables. + token0Address = await pool.token0(); + token0 = new Contract(token0Address, TESTERC20_ABI, signer); + token1Address = await pool.token1(); + token1 = new Contract(token1Address, TESTERC20_ABI, signer); }); }); @@ -142,7 +167,7 @@ describe('uni-info-watcher', () => { expect(data.pool.tick).to.be.null; }); - it('should initialize pool', async () => { + it('should trigger InitializeEvent', async () => { initializePool(pool, sqrtPrice); // Wait for InitializeEvent. @@ -183,4 +208,141 @@ describe('uni-info-watcher', () => { expect(tvlUSD).to.be.equal(totalValueLockedUSD); }); }); + + describe('MintEvent', () => { + const amount = 10; + const approveAmount = BigInt(1000000000000000000000000); + let poolCallee: Contract; + let tickLower: number; + let tickUpper: number; + + // Initial entity values + let oldFactory: any; + let oldToken0: any; + let oldToken1: any; + let oldPool: any; + + before(async () => { + // Deploy UniswapV3Callee. + poolCallee = await deployUniswapV3Callee(signer); + + const tickSpacing = await pool.tickSpacing(); + // https://github.com/Uniswap/uniswap-v3-core/blob/main/test/UniswapV3Pool.spec.ts#L196 + tickLower = getMinTick(tickSpacing); + tickUpper = getMaxTick(tickSpacing); + + await approveToken(token0, poolCallee.address, approveAmount); + await approveToken(token1, poolCallee.address, approveAmount); + + // Get initial entity values. + let data: any; + + data = await request(endpoint, queryFactory); + oldFactory = data.factories[0]; + + data = await request(endpoint, queryToken, { id: token0.address }); + oldToken0 = data.token; + + data = await request(endpoint, queryToken, { id: token1.address }); + oldToken1 = data.token; + + data = await request(endpoint, queryPoolById, { id: pool.address }); + oldPool = data.pool; + }); + + it('should trigger MintEvent', async () => { + // Pool mint. + const transaction: ContractTransaction = await poolCallee.mint(pool.address, recipient, BigInt(tickLower), BigInt(tickUpper), BigInt(amount)); + await transaction.wait(); + + // Wait for MintEvent. + const eventType = 'MintEvent'; + await watchEvent(uniClient, eventType); + + // Sleeping for 20 sec for the entities to be processed. + await wait(20000); + }); + + it('should update Token entities', async () => { + // Check txCount. + let data: any; + + data = await request(endpoint, queryToken, { id: token0.address }); + const newToken0 = data.token; + + data = await request(endpoint, queryToken, { id: token1.address }); + const newToken1 = data.token; + + expect(newToken0.txCount).to.be.equal((BigInt(oldToken0.txCount) + BigInt(1)).toString()); + expect(newToken1.txCount).to.be.equal((BigInt(oldToken1.txCount) + BigInt(1)).toString()); + }); + + it('should update Factory entity', async () => { + // Check txCount. + const data = await request(endpoint, queryFactory); + const newFactory = data.factories[0]; + expect(newFactory.txCount).to.be.equal((BigInt(oldFactory.txCount) + BigInt(1)).toString()); + }); + + it('should update Pool entity', async () => { + // Check txCount, liquidity. + let expectedLiquidity = BigInt(oldPool.liquidity); + if (oldPool.tick !== null) { + if ( + BigInt(tickLower) <= BigInt(oldPool.tick) && + BigInt(tickUpper) > BigInt(oldPool.tick) + ) { + expectedLiquidity = BigInt(oldPool.liquidity) + BigInt(amount); + } + } + + const data = await request(endpoint, queryPoolById, { id: pool.address }); + const newPool = data.pool; + + expect(newPool.txCount).to.be.equal((BigInt(oldPool.txCount) + BigInt(1)).toString()); + expect(BigInt(newPool.liquidity)).to.be.equal(expectedLiquidity); + }); + + it('should create a Mint entity', async () => { + // Check id, owner, sender. + // Get the latest Mint. + let data: any; + const variables = { + first: 1, + orderBy: 'timestamp', + orderDirection: 'desc', + pool: pool.address, + }; + data = await request(endpoint, queryMints, variables); + expect(data.mints).to.not.be.empty; + + const id: string = data.mints[0].id; + const txCountID = id.split('#')[1]; + const owner = data.mints[0].owner; + const sender = data.mints[0].sender; + + data = await request(endpoint, queryPoolById, { id: pool.address }); + const poolTxCount = data.pool.txCount; + const expectedOwner = recipient; + const expectedSender = poolCallee.address; + + expect(txCountID).to.be.equal(poolTxCount); + expect(owner).to.be.equal(expectedOwner); + expect(sender).to.be.equal(expectedSender); + }); + + it('should create Tick entities', async () => { + // Check liquidityGross, liquidityNet. + const data = await request(endpoint, queryTicks, { pool: pool.address }); + expect(data.ticks).to.not.be.empty; + + const lowerTick: any = _.filter(data.ticks, { tickIdx: tickLower.toString() })[0]; + const upperTick: any = _.filter(data.ticks, { tickIdx: tickUpper.toString() })[0]; + + expect(lowerTick.liquidityGross).to.be.equal(amount.toString()); + expect(lowerTick.liquidityNet).to.be.equal(amount.toString()); + expect(upperTick.liquidityGross).to.be.equal(amount.toString()); + expect(upperTick.liquidityNet).to.be.equal(amount.toString()); + }); + }); }); diff --git a/packages/uni-info-watcher/test/queries.ts b/packages/uni-info-watcher/test/queries.ts index da84bda9..f4534098 100644 --- a/packages/uni-info-watcher/test/queries.ts +++ b/packages/uni-info-watcher/test/queries.ts @@ -3,7 +3,16 @@ import { gql } from 'graphql-request'; export const queryToken = gql` query queryToken($id: ID!) { token(id: $id) { + derivedETH + feesUSD id + name + symbol + totalValueLocked + totalValueLockedUSD + txCount + volume + volumeUSD } }`; @@ -12,6 +21,10 @@ export const queryFactory = gql` { factories(first: 1) { id + totalFeesUSD + totalValueLockedUSD + totalVolumeUSD + txCount } }`; @@ -20,6 +33,7 @@ export const queryBundle = gql` { bundles(first: 1) { id + ethPriceUSD } }`; @@ -27,10 +41,18 @@ export const queryBundle = gql` export const queryPoolById = gql` query queryPoolById($id: ID!) { pool(id: $id) { - id, - sqrtPrice, - tick, + feeTier + id + liquidity + sqrtPrice + tick + token0Price + token1Price + totalValueLockedToken0 + totalValueLockedToken1 totalValueLockedUSD + txCount + volumeUSD } }`; @@ -52,3 +74,45 @@ query queryPoolDayData($first: Int, $orderBy: PoolDayData_orderBy, $orderDirecti tvlUSD } }`; + +// Getting mint(s) filtered by pool, tokens and ordered by timestamp. +export const queryMints = gql` +query queryMints( + $first: Int, + $orderBy: Mint_orderBy, + $orderDirection: OrderDirection, + $pool: String, + $token0: String, + $token1: String) { + mints( + first: $first, + orderBy: $orderBy, + orderDirection: $orderDirection, + where: { + pool: $pool, + token0: $token0, + token1: $token1 + }) { + amount0 + amount1 + amountUSD + id + origin + owner + sender + timestamp + } +}`; + +// Getting Tick(s) filtered by pool. +export const queryTicks = gql` +query queryTicksByPool($pool: String) { + ticks(where: { poolAddress: $pool }) { + id + liquidityGross + liquidityNet + price0 + price1 + tickIdx + } +}`; diff --git a/packages/uni-watcher/.mocharc.yml b/packages/uni-watcher/.mocharc.yml index 71cc8a79..9bc76859 100644 --- a/packages/uni-watcher/.mocharc.yml +++ b/packages/uni-watcher/.mocharc.yml @@ -1,4 +1,4 @@ -timeout: '60000' +timeout: '70000' bail: true exit: true # TODO: Find out why the program doesn't exit on its own. require: 'ts-node/register' diff --git a/packages/uni-watcher/src/smoke.test.ts b/packages/uni-watcher/src/smoke.test.ts index 88f313e5..0b9b0644 100644 --- a/packages/uni-watcher/src/smoke.test.ts +++ b/packages/uni-watcher/src/smoke.test.ts @@ -2,7 +2,16 @@ import { expect, assert } from 'chai'; import { ethers, Contract, ContractTransaction, Signer, constants } from 'ethers'; import 'mocha'; -import { Config, getConfig, deployTokens, TESTERC20_ABI } from '@vulcanize/util'; +import { + Config, + getConfig, + deployTokens, + deployUniswapV3Callee, + TESTERC20_ABI, + getMinTick, + getMaxTick, + approveToken +} from '@vulcanize/util'; import { Client as UniClient } from '@vulcanize/uni-watcher'; import { getCache } from '@vulcanize/cache'; import { EthClient } from '@vulcanize/ipld-eth-client'; @@ -42,10 +51,6 @@ import { checkDecreaseLiquidityEvent, checksCollectEvent } from '../test/utils'; -import { - abi as TESTUNISWAPV3CALLEE_ABI, - bytecode as TESTUNISWAPV3CALLEE_BYTECODE -} from '../artifacts/test/contracts/TestUniswapV3Callee.sol/TestUniswapV3Callee.json'; import { abi as WETH9_ABI, bytecode as WETH9_BYTECODE @@ -55,14 +60,13 @@ const NETWORK_RPC_URL = 'http://localhost:8545'; const TICK_MIN = -887272; const TICK_MAX = 887272; -const getMinTick = (tickSpacing: number) => Math.ceil(TICK_MIN / tickSpacing) * tickSpacing; -const getMaxTick = (tickSpacing: number) => Math.floor(TICK_MAX / tickSpacing) * tickSpacing; describe('uni-watcher', () => { let factory: Contract; let pool: Contract; let token0: Contract; let token1: Contract; + let poolCallee: Contract; let token0Address: string; let token1Address: string; let weth9Address: string; @@ -161,6 +165,12 @@ describe('uni-watcher', () => { token0 = new Contract(token0Address, TESTERC20_ABI, signer); token1Address = await pool.token1(); token1 = new Contract(token1Address, TESTERC20_ABI, signer); + + // Initializing ticks. + const tickSpacing = await pool.tickSpacing(); + // https://github.com/Uniswap/uniswap-v3-core/blob/main/test/UniswapV3Pool.spec.ts#L196 + tickLower = getMinTick(tickSpacing); + tickUpper = getMaxTick(tickSpacing); }); it('should initialize pool', async () => { @@ -174,21 +184,11 @@ describe('uni-watcher', () => { const amount = 10; const approveAmount = BigInt(1000000000000000000000000); - const TestUniswapV3Callee = new ethers.ContractFactory(TESTUNISWAPV3CALLEE_ABI, TESTUNISWAPV3CALLEE_BYTECODE, signer); - const poolCallee = await TestUniswapV3Callee.deploy(); + // Deploy UniswapV3Callee. + poolCallee = await deployUniswapV3Callee(signer); - const tickSpacing = await pool.tickSpacing(); - // https://github.com/Uniswap/uniswap-v3-core/blob/main/test/UniswapV3Pool.spec.ts#L196 - tickLower = getMinTick(tickSpacing); - tickUpper = getMaxTick(tickSpacing); - - // Approving tokens for TestUniswapV3Callee contract. - // https://github.com/Uniswap/uniswap-v3-core/blob/main/test/shared/utilities.ts#L187 - const t0 = await token0.approve(poolCallee.address, approveAmount); - await t0.wait(); - - const t1 = await token1.approve(poolCallee.address, approveAmount); - await t1.wait(); + await approveToken(token0, poolCallee.address, approveAmount); + await approveToken(token1, poolCallee.address, approveAmount); // Subscribe using UniClient. const subscription = await uniClient.watchEvents((value: any) => { @@ -218,11 +218,6 @@ describe('uni-watcher', () => { (async () => { const amount = 10; - const tickSpacing = await pool.tickSpacing(); - // https://github.com/Uniswap/uniswap-v3-core/blob/main/test/UniswapV3Pool.spec.ts#L196 - const tickLower = getMinTick(tickSpacing); - const tickUpper = getMaxTick(tickSpacing); - // Subscribe using UniClient. const subscription = await uniClient.watchEvents((value: any) => { if (value.event.__typename === 'BurnEvent') { @@ -250,9 +245,6 @@ describe('uni-watcher', () => { (async () => { const sqrtPrice = '4295128938'; - const TestUniswapV3Callee = new ethers.ContractFactory(TESTUNISWAPV3CALLEE_ABI, TESTUNISWAPV3CALLEE_BYTECODE, signer); - const poolCallee = await TestUniswapV3Callee.deploy(); - // Subscribe using UniClient. const subscription = await uniClient.watchEvents((value: any) => { if (value.event.__typename === 'SwapEvent') { @@ -330,6 +322,11 @@ describe('uni-watcher', () => { const fee = 3000; pool = await testCreatePool(uniClient, factory, token0Address, token1Address, fee, POOL_ABI, signer); + const tickSpacing = await pool.tickSpacing(); + // https://github.com/Uniswap/uniswap-v3-core/blob/main/test/UniswapV3Pool.spec.ts#L196 + tickLower = getMinTick(tickSpacing); + tickUpper = getMaxTick(tickSpacing); + const sqrtPrice = '79228162514264337593543950336'; await testInitialize(uniClient, pool, sqrtPrice, 0); @@ -339,11 +336,6 @@ describe('uni-watcher', () => { const amount1Min = 0; const deadline = 1634367993; - const tickSpacing = await pool.tickSpacing(); - // https://github.com/Uniswap/uniswap-v3-core/blob/main/test/UniswapV3Pool.spec.ts#L196 - tickLower = getMinTick(tickSpacing); - tickUpper = getMaxTick(tickSpacing); - // Approving tokens for NonfungiblePositionManager contract. // https://github.com/Uniswap/uniswap-v3-periphery/blob/main/test/NonfungiblePositionManager.spec.ts#L44 const t0 = await token0.approve(nfpm.address, constants.MaxUint256); @@ -487,7 +479,7 @@ describe('uni-watcher', () => { }); }); - it('should collect fees', done => { + xit('should collect fees', done => { (async () => { const tokenId = 1; const amount0Max = 15; diff --git a/packages/util/test/actions.ts b/packages/util/test/actions.ts index 0cef07e5..e62c3c4d 100644 --- a/packages/util/test/actions.ts +++ b/packages/util/test/actions.ts @@ -4,9 +4,18 @@ import { abi as TESTERC20_ABI, bytecode as TESTERC20_BYTECODE } from '../artifacts/test/contracts/TestERC20.sol/TestERC20.json'; +import { + abi as TESTUNISWAPV3CALLEE_ABI, + bytecode as TESTUNISWAPV3CALLEE_BYTECODE +} from '../artifacts/test/contracts/TestUniswapV3Callee.sol/TestUniswapV3Callee.json'; export { abi as TESTERC20_ABI } from '../artifacts/test/contracts/TestERC20.sol/TestERC20.json'; +const TICK_MIN = -887272; +const TICK_MAX = 887272; +export const getMinTick = (tickSpacing: number) => Math.ceil(TICK_MIN / tickSpacing) * tickSpacing; +export const getMaxTick = (tickSpacing: number) => Math.floor(TICK_MAX / tickSpacing) * tickSpacing; + export const deployTokens = async (signer: Signer): Promise<{token0Address: string, token1Address: string}> => { const Token = new ethers.ContractFactory(TESTERC20_ABI, TESTERC20_BYTECODE, signer); @@ -19,6 +28,16 @@ export const deployTokens = async (signer: Signer): Promise<{token0Address: stri return { token0Address, token1Address }; }; +export const deployUniswapV3Callee = async (signer: Signer): Promise => { + const TestUniswapV3Callee = new ethers.ContractFactory(TESTUNISWAPV3CALLEE_ABI, TESTUNISWAPV3CALLEE_BYTECODE, signer); + return await TestUniswapV3Callee.deploy(); +}; + +export const approveToken = async (token: Contract, address: string, approveAmount: bigint): Promise => { + const transaction: ContractTransaction = await token.approve(address, approveAmount); + await transaction.wait(); +}; + export const createPool = async ( factory: Contract, token0Address: string, diff --git a/packages/uni-watcher/test/contracts/TestUniswapV3Callee.sol b/packages/util/test/contracts/TestUniswapV3Callee.sol similarity index 100% rename from packages/uni-watcher/test/contracts/TestUniswapV3Callee.sol rename to packages/util/test/contracts/TestUniswapV3Callee.sol