From 0c2efde028bbf28c5846b7463ee038f679bebd77 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Tue, 10 Aug 2021 12:09:00 +0530 Subject: [PATCH] Test for entities after TransferEvent (#196) * Add resolver for Position entity in uni-info-watcher. * Test for entities after TransferEvent. Co-authored-by: prathamesh0 --- packages/uni-info-watcher/.mocharc.yml | 2 +- packages/uni-info-watcher/src/indexer.ts | 2 +- packages/uni-info-watcher/src/resolvers.ts | 7 + packages/uni-info-watcher/src/schema.ts | 27 ++++ packages/uni-info-watcher/src/smoke.test.ts | 140 ++++++++++++++++-- packages/uni-info-watcher/test/queries.ts | 34 +++++ packages/uni-watcher/.eslintrc.json | 3 +- packages/uni-watcher/hardhat.config.ts | 3 - packages/uni-watcher/src/client.ts | 13 +- packages/uni-watcher/src/indexer.ts | 7 + packages/uni-watcher/src/queries.ts | 8 + packages/uni-watcher/src/resolvers.ts | 5 + packages/uni-watcher/src/schema.ts | 8 + packages/uni-watcher/src/smoke.test.ts | 59 +------- packages/uni-watcher/test/utils.ts | 35 +---- packages/util/hardhat.config.ts | 3 + packages/util/test/actions.ts | 88 ++++++++++- .../test/contracts/WETH9.sol | 0 18 files changed, 342 insertions(+), 102 deletions(-) rename packages/{uni-watcher => util}/test/contracts/WETH9.sol (100%) diff --git a/packages/uni-info-watcher/.mocharc.yml b/packages/uni-info-watcher/.mocharc.yml index e1238532..71cc8a79 100644 --- a/packages/uni-info-watcher/.mocharc.yml +++ b/packages/uni-info-watcher/.mocharc.yml @@ -1,4 +1,4 @@ -timeout: '50000' +timeout: '60000' 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/indexer.ts b/packages/uni-info-watcher/src/indexer.ts index 5b50617f..500b8800 100644 --- a/packages/uni-info-watcher/src/indexer.ts +++ b/packages/uni-info-watcher/src/indexer.ts @@ -1139,7 +1139,7 @@ export class Indexer { const [token0, token1] = await Promise.all([ this._db.getTokenNoTx({ id: token0Address, blockHash }), - this._db.getTokenNoTx({ id: token0Address, blockHash }) + this._db.getTokenNoTx({ id: token1Address, blockHash }) ]); assert(token0 && token1); position.token0 = token0; diff --git a/packages/uni-info-watcher/src/resolvers.ts b/packages/uni-info-watcher/src/resolvers.ts index 97521f73..b8af48e5 100644 --- a/packages/uni-info-watcher/src/resolvers.ts +++ b/packages/uni-info-watcher/src/resolvers.ts @@ -16,6 +16,7 @@ import { TokenDayData } from './entity/TokenDayData'; import { TokenHourData } from './entity/TokenHourData'; import { Transaction } from './entity/Transaction'; import { UniswapDayData } from './entity/UniswapDayData'; +import { Position } from './entity/Position'; const log = debug('vulcanize:resolver'); @@ -122,6 +123,12 @@ export const createResolvers = async (indexer: Indexer): Promise => { log('uniswapDayDatas', first, skip, orderBy, orderDirection, where); return indexer.getEntities(UniswapDayData, {}, where, { limit: first, skip, orderBy, orderDirection }); + }, + + positions: async (_: any, { first, where }: { first: number, where: { [key: string]: any } }) => { + log('uniswapDayDatas', first, where); + + return indexer.getEntities(Position, {}, where, { limit: first }, ['pool', 'token0', 'token1', 'tickLower', 'tickUpper', 'transaction']); } } }; diff --git a/packages/uni-info-watcher/src/schema.ts b/packages/uni-info-watcher/src/schema.ts index f94a3aed..5c23e3aa 100644 --- a/packages/uni-info-watcher/src/schema.ts +++ b/packages/uni-info-watcher/src/schema.ts @@ -138,6 +138,24 @@ type TokenHourData { periodStartUnix: Int! } +type Position { + id: ID! + pool: Pool! + token0: Token! + token1: Token! + tickLower: Tick! + tickUpper: Tick! + transaction: Transaction! + liquidity: BigInt! + depositedToken0: BigDecimal! + depositedToken1: BigDecimal! + collectedFeesToken0: BigDecimal! + collectedFeesToken1: BigDecimal! + owner: Bytes! + feeGrowthInside0LastX128: BigInt! + feeGrowthInside1LastX128: BigInt! +} + enum OrderDirection { asc desc @@ -242,6 +260,10 @@ enum TokenHourData_orderBy { periodStartUnix } +input Position_filter { + id: ID +} + type Query { bundle( id: ID! @@ -382,5 +404,10 @@ type Query { orderDirection: OrderDirection where: UniswapDayData_filter ): [UniswapDayData!]! + + positions( + first: Int = 100 + where: Position_filter + ): [Position!]! } `; diff --git a/packages/uni-info-watcher/src/smoke.test.ts b/packages/uni-info-watcher/src/smoke.test.ts index fd9b0a1f..e9533d68 100644 --- a/packages/uni-info-watcher/src/smoke.test.ts +++ b/packages/uni-info-watcher/src/smoke.test.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ethers, Contract, Signer } from 'ethers'; +import { ethers, Contract, Signer, constants } from 'ethers'; import { request } from 'graphql-request'; import 'mocha'; import _ from 'lodash'; @@ -18,7 +18,8 @@ import { initializePool, getMinTick, getMaxTick, - approveToken + approveToken, + NFPM_ABI } from '@vulcanize/util/test'; import { Client as UniClient, watchEvent } from '@vulcanize/uni-watcher'; import { @@ -37,7 +38,8 @@ import { queryMints, queryTicks, queryBurns, - querySwaps + querySwaps, + queryPositions } from '../test/queries'; import { checkUniswapDayData, @@ -57,6 +59,7 @@ describe('uni-info-watcher', () => { let token1: Contract; let token0Address: string; let token1Address: string; + let nfpm: Contract; let tickLower: number; let tickUpper: number; @@ -105,7 +108,7 @@ describe('uni-info-watcher', () => { }); describe('PoolCreatedEvent', () => { - // NOTE Skipping checking entity updates that cannot be gotten/derived using queries. + // NOTE: Skipping checking entity updates that cannot be gotten/derived using queries. // Checked entities: Token, Pool. const fee = 500; @@ -135,7 +138,7 @@ describe('uni-info-watcher', () => { const eventType = 'PoolCreatedEvent'; await watchEvent(uniClient, eventType); - // Sleeping for 10 sec for the entities to be processed. + // Sleeping for 10 sec for the event to be processed. await wait(10000); }); @@ -187,13 +190,14 @@ describe('uni-info-watcher', () => { }); it('should trigger InitializeEvent', async () => { + // Initialize Pool. initializePool(pool, sqrtPrice); // Wait for InitializeEvent. const eventType = 'InitializeEvent'; await watchEvent(uniClient, eventType); - // Sleeping for 5 sec for the entities to be processed. + // Sleeping for 5 sec for the event to be processed. await wait(5000); }); @@ -260,7 +264,7 @@ describe('uni-info-watcher', () => { const eventType = 'MintEvent'; await watchEvent(uniClient, eventType); - // Sleeping for 20 sec for the entities to be processed. + // Sleeping for 20 sec for the event to be processed. await wait(20000); }); @@ -432,7 +436,7 @@ describe('uni-info-watcher', () => { const eventType = 'BurnEvent'; await watchEvent(uniClient, eventType); - // Sleeping for 15 sec for the entities to be processed. + // Sleeping for 15 sec for the event to be processed. await wait(15000); }); @@ -602,7 +606,7 @@ describe('uni-info-watcher', () => { const eventType = 'SwapEvent'; eventValue = await watchEvent(uniClient, eventType); - // Sleeping for 5 sec for the entities to be processed. + // Sleeping for 5 sec for the event to be processed. await wait(5000); }); @@ -703,4 +707,122 @@ describe('uni-info-watcher', () => { checkTokenHourData(endpoint, token1.address); }); }); + + describe('TransferEvent', () => { + // NOTE: The test cases for TransferEvent are written such that IncreaseLiquidityEvent has also been processed right after. + // Checked entities: Transaction, Position. + + const fee = 3000; + const sqrtPrice = '79228162514264337593543950336'; + let eventType: string; + let eventValue: any; + let expectedTxID: string; + + const amount0Desired = 15; + const amount1Desired = 15; + const amount0Min = 0; + const amount1Min = 0; + const deadline = 1634367993; + + before(async () => { + // Get the NFPM contract. + const latestContract = await uniClient.getLatestContract('nfpm'); + expect(latestContract.address).to.not.be.empty; + + nfpm = new Contract(latestContract.address, NFPM_ABI, signer); + + // Create Pool. + createPool(factory, token0Address, token1Address, fee); + + // Wait for PoolCreatedEvent. + eventType = 'PoolCreatedEvent'; + eventValue = await watchEvent(uniClient, eventType); + + // Sleeping for 10 sec for the event to be processed. + await wait(10000); // not needed + + // Reinitializing the pool variable. + const poolAddress = eventValue.event.pool; + pool = new Contract(poolAddress, POOL_ABI, signer); + expect(pool.address).to.not.be.empty; + + // Reinitializing the ticks + const tickSpacing = await pool.tickSpacing(); + tickLower = getMinTick(tickSpacing); + tickUpper = getMaxTick(tickSpacing); + + // Initialize Pool. + initializePool(pool, sqrtPrice); + + // Wait for InitializeEvent. + eventType = 'InitializeEvent'; + await watchEvent(uniClient, eventType); + + // Sleeping for 5 sec for the event to be processed. + await wait(5000); + + // Approving tokens for NonfungiblePositionManager contract. + await approveToken(token0, nfpm.address, BigInt(constants.MaxUint256.toString())); + await approveToken(token1, nfpm.address, BigInt(constants.MaxUint256.toString())); + }); + + it('should trigger TransferEvent', async () => { + nfpm.mint({ + token0: token0Address, + token1: token1Address, + tickLower, + tickUpper, + amount0Desired, + amount1Desired, + amount0Min, + amount1Min, + recipient, + deadline, + fee + }); + + // Wait for MintEvent. + eventType = 'MintEvent'; + await watchEvent(uniClient, eventType); + + // Wait for TransferEvent. + eventType = 'TransferEvent'; + eventValue = await watchEvent(uniClient, eventType); + + // Wait for IncreaseLiquidityEvent. + eventType = 'IncreaseLiquidityEvent'; + await watchEvent(uniClient, eventType); + + // Sleeping for 15 sec for the events to be processed. + await wait(15000); + }); + + it('should create a Transaction entity', async () => { + const eventType = 'MintEvent'; + ({ expectedTxID } = await checkTransaction(endpoint, eventType)); + }); + + it('should createa a Position entity', async () => { + // Checked values: pool, token0, token1, tickLower, tickUpper, transaction, owner. + // Unchecked values: feeGrowthInside0LastX128, feeGrowthInside0LastX128. + + // Get the Position using tokenId. + const data = await request(endpoint, queryPositions, { id: Number(eventValue.event.tokenId) }); + expect(data.positions).to.not.be.empty; + + const position = data.positions[0]; + const positionTickLower = position.tickLower.id.split('#')[1]; + const positionTickUpper = position.tickUpper.id.split('#')[1]; + + const expectedOwner = eventValue.event.to; + + expect(position.pool.id).to.be.equal(pool.address); + expect(position.token0.id).to.be.equal(token0.address); + expect(position.token1.id).to.be.equal(token1.address); + expect(positionTickLower).to.be.equal(tickLower.toString()); + expect(positionTickUpper).to.be.equal(tickUpper.toString()); + expect(position.transaction.id).to.be.equal(expectedTxID); + expect(position.owner).to.be.equal(expectedOwner); + }); + }); }); diff --git a/packages/uni-info-watcher/test/queries.ts b/packages/uni-info-watcher/test/queries.ts index aff56d89..ba0f3e92 100644 --- a/packages/uni-info-watcher/test/queries.ts +++ b/packages/uni-info-watcher/test/queries.ts @@ -252,3 +252,37 @@ query queryTransactions( timestamp } }`; + +// Getting position filtered by id. +export const queryPositions = gql` +query queryPositions($first: Int, $id: ID) { + positions(first: $first, where: { id: $id }) { + id, + pool { + id + }, + token0 { + id + }, + token1 { + id + }, + tickLower { + id + }, + tickUpper { + id + }, + transaction { + id + }, + liquidity, + depositedToken0, + depositedToken1, + collectedFeesToken0, + collectedFeesToken1, + owner, + feeGrowthInside0LastX128, + feeGrowthInside1LastX128 + } +}`; diff --git a/packages/uni-watcher/.eslintrc.json b/packages/uni-watcher/.eslintrc.json index a6246e7d..853d994a 100644 --- a/packages/uni-watcher/.eslintrc.json +++ b/packages/uni-watcher/.eslintrc.json @@ -28,8 +28,7 @@ { "files": ["*.test.ts", "test/*.ts"], "rules": { - "no-unused-expressions": "off", - "no-prototype-builtins": "off" + "no-unused-expressions": "off" } } ] diff --git a/packages/uni-watcher/hardhat.config.ts b/packages/uni-watcher/hardhat.config.ts index 35dc8be8..d8509ad8 100644 --- a/packages/uni-watcher/hardhat.config.ts +++ b/packages/uni-watcher/hardhat.config.ts @@ -7,9 +7,6 @@ const config: HardhatUserConfig = { compilers: [ { version: '0.7.6' - }, - { - version: '0.5.0' } ] }, diff --git a/packages/uni-watcher/src/client.ts b/packages/uni-watcher/src/client.ts index a1c7555b..769a3336 100644 --- a/packages/uni-watcher/src/client.ts +++ b/packages/uni-watcher/src/client.ts @@ -1,7 +1,7 @@ import { gql } from '@apollo/client/core'; import { GraphQLClient, GraphQLConfig } from '@vulcanize/ipld-eth-client'; -import { queryGetPool, queryPoolIdToPoolKey, queryPosition, queryEvents, subscribeEvents } from './queries'; +import { queryGetPool, queryPoolIdToPoolKey, queryPosition, queryEvents, subscribeEvents, queryLatestContract } from './queries'; export class Client { _config: GraphQLConfig; @@ -71,4 +71,15 @@ export class Client { return getPool; } + + async getLatestContract (type: string): Promise { + const { latestContract } = await this._client.query( + gql(queryLatestContract), + { + type + } + ); + + return latestContract; + } } diff --git a/packages/uni-watcher/src/indexer.ts b/packages/uni-watcher/src/indexer.ts index 9ea163bf..47d2755d 100644 --- a/packages/uni-watcher/src/indexer.ts +++ b/packages/uni-watcher/src/indexer.ts @@ -604,4 +604,11 @@ export class Indexer { ...mappingKeys ); } + + async getLatestContract (type: string): Promise { + const contract = await this._db.getLatestContract(type); + assert(contract, `No ${type} contract watched.`); + + return contract; + } } diff --git a/packages/uni-watcher/src/queries.ts b/packages/uni-watcher/src/queries.ts index 08fda8b8..4ba2d79a 100644 --- a/packages/uni-watcher/src/queries.ts +++ b/packages/uni-watcher/src/queries.ts @@ -155,3 +155,11 @@ query getPool($blockHash: String!, $token0: String!, $token1: String!, $fee: Str } } `; + +export const queryLatestContract = gql` +query queryLatestContract($type: String!) { + latestContract(type: $type) { + address + } +} +`; diff --git a/packages/uni-watcher/src/resolvers.ts b/packages/uni-watcher/src/resolvers.ts index 0cc30102..0d1bb15e 100644 --- a/packages/uni-watcher/src/resolvers.ts +++ b/packages/uni-watcher/src/resolvers.ts @@ -89,6 +89,11 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch getPool: (_: any, { blockHash, token0, token1, fee }: { blockHash: string, token0: string, token1: string, fee: string }) => { log('getPool', blockHash, token0, token1, fee); return indexer.getPool(blockHash, token0, token1, fee); + }, + + latestContract: (_: any, { type }: { type: string }) => { + log('latestContract', type); + return indexer.getLatestContract(type); } } }; diff --git a/packages/uni-watcher/src/schema.ts b/packages/uni-watcher/src/schema.ts index 6f6b0f50..e79eb337 100644 --- a/packages/uni-watcher/src/schema.ts +++ b/packages/uni-watcher/src/schema.ts @@ -193,6 +193,10 @@ type BlockProgressEvent { isComplete: Boolean! } +type Contract { + address: String! +} + # # Queries # @@ -201,6 +205,10 @@ type Query { # NonfungiblePositionManager + latestContract( + type: String! + ): Contract + position( blockHash: String! tokenId: String! diff --git a/packages/uni-watcher/src/smoke.test.ts b/packages/uni-watcher/src/smoke.test.ts index 205f44af..a6f154a0 100644 --- a/packages/uni-watcher/src/smoke.test.ts +++ b/packages/uni-watcher/src/smoke.test.ts @@ -13,7 +13,9 @@ import { TICK_MIN, getMinTick, getMaxTick, - approveToken + approveToken, + deployWETH9Token, + deployNFPM } from '@vulcanize/util/test'; import { Client as UniClient } from '@vulcanize/uni-watcher'; import { getCache } from '@vulcanize/cache'; @@ -25,25 +27,11 @@ import { import { abi as POOL_ABI } from '@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json'; -import { - abi as NFTD_ABI, - bytecode as NFTD_BYTECODE -} from '@uniswap/v3-periphery/artifacts/contracts/libraries/NFTDescriptor.sol/NFTDescriptor.json'; -import { - abi as NFTPD_ABI, - bytecode as NFTPD_BYTECODE, - linkReferences as NFTPD_LINKREFS -} from '@uniswap/v3-periphery/artifacts/contracts/NonfungibleTokenPositionDescriptor.sol/NonfungibleTokenPositionDescriptor.json'; -import { - abi as NFPM_ABI, - bytecode as NFPM_BYTECODE -} from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'; import { Indexer } from './indexer'; import { Database } from './database'; import { watchContract } from './utils/index'; import { - linkLibraries, testCreatePool, testInitialize, checkMintEvent, @@ -54,10 +42,6 @@ import { checkDecreaseLiquidityEvent, checksCollectEvent } from '../test/utils'; -import { - abi as WETH9_ABI, - bytecode as WETH9_BYTECODE -} from '../artifacts/test/contracts/WETH9.sol/WETH9.json'; const NETWORK_RPC_URL = 'http://localhost:8545'; @@ -270,47 +254,18 @@ describe('uni-watcher', () => { it('should deploy a WETH9 token', async () => { // Deploy weth9 token. - const WETH9 = new ethers.ContractFactory(WETH9_ABI, WETH9_BYTECODE, signer); - const weth9 = await WETH9.deploy(); - - weth9Address = weth9.address; - expect(weth9.address).to.not.be.empty; + weth9Address = await deployWETH9Token(signer); + expect(weth9Address).to.not.be.empty; }); it('should deploy NonfungiblePositionManager', async () => { // Deploy NonfungiblePositionManager. - // https://github.com/Uniswap/uniswap-v3-periphery/blob/main/test/shared/completeFixture.ts#L31 - const nftDescriptorLibraryFactory = new ethers.ContractFactory(NFTD_ABI, NFTD_BYTECODE, signer); - const nftDescriptorLibrary = await nftDescriptorLibraryFactory.deploy(); - expect(nftDescriptorLibrary.address).to.not.be.empty; - - // Linking NFTDescriptor library to NFTPD before deploying. - const linkedNFTPDBytecode = linkLibraries({ - bytecode: NFTPD_BYTECODE, - linkReferences: NFTPD_LINKREFS - }, { - NFTDescriptor: nftDescriptorLibrary.address - } - ); - - const positionDescriptorFactory = new ethers.ContractFactory( - NFTPD_ABI, - linkedNFTPDBytecode, - signer); - const nftDescriptor = await positionDescriptorFactory.deploy(weth9Address); - expect(nftDescriptor.address).to.not.be.empty; - - const positionManagerFactory = new ethers.ContractFactory( - NFPM_ABI, - NFPM_BYTECODE, - signer); - nfpm = await positionManagerFactory.deploy(factory.address, weth9Address, nftDescriptor.address); - + nfpm = await deployNFPM(signer, factory, weth9Address); expect(nfpm.address).to.not.be.empty; }); it('should watch NonfungiblePositionManager contract', async () => { - // Watch factory contract. + // Watch NFPM contract. await watchContract(db, nfpm.address, 'nfpm', 100); // Verifying with the db. diff --git a/packages/uni-watcher/test/utils.ts b/packages/uni-watcher/test/utils.ts index ac1be03d..103b9f50 100644 --- a/packages/uni-watcher/test/utils.ts +++ b/packages/uni-watcher/test/utils.ts @@ -1,39 +1,10 @@ -import { ethers, utils, Contract, Signer } from 'ethers'; +import { ethers, Contract, Signer } from 'ethers'; import { expect } from 'chai'; import 'mocha'; import { Client as UniClient } from '@vulcanize/uni-watcher'; import { createPool, initializePool } from '@vulcanize/util/test'; -// https://github.com/ethers-io/ethers.js/issues/195 -export const linkLibraries = ( - { - bytecode, - linkReferences - }: { - bytecode: string - linkReferences: { [fileName: string]: { [contractName: string]: { length: number; start: number }[] } } - }, - libraries: { [libraryName: string]: string }): string => { - Object.keys(linkReferences).forEach((fileName) => { - Object.keys(linkReferences[fileName]).forEach((contractName) => { - if (!libraries.hasOwnProperty(contractName)) { - throw new Error(`Missing link library name ${contractName}`); - } - const address = utils.getAddress(libraries[contractName]).toLowerCase().slice(2); - linkReferences[fileName][contractName].forEach(({ start: byteStart, length: byteLength }) => { - const start = 2 + byteStart * 2; - const length = byteLength * 2; - bytecode = bytecode - .slice(0, start) - .concat(address) - .concat(bytecode.slice(start + length, bytecode.length)); - }); - }); - }); - return bytecode; -}; - export const testCreatePool = async ( uniClient: UniClient, factory: Contract, @@ -201,13 +172,13 @@ export const checkTransferEvent = ( value: any, expectedContract: string, from: string, - recipient: string + to: string ): void => { checkEventCommonValues(value, expectedContract); expect(value.event.__typename).to.equal('TransferEvent'); expect(value.event.from).to.equal(from); - expect(value.event.to).to.equal(recipient); + expect(value.event.to).to.equal(to); expect(value.event.tokenId).to.equal('1'); }; diff --git a/packages/util/hardhat.config.ts b/packages/util/hardhat.config.ts index d8509ad8..35dc8be8 100644 --- a/packages/util/hardhat.config.ts +++ b/packages/util/hardhat.config.ts @@ -7,6 +7,9 @@ const config: HardhatUserConfig = { compilers: [ { version: '0.7.6' + }, + { + version: '0.5.0' } ] }, diff --git a/packages/util/test/actions.ts b/packages/util/test/actions.ts index 8778ee8a..46508566 100644 --- a/packages/util/test/actions.ts +++ b/packages/util/test/actions.ts @@ -1,4 +1,19 @@ -import { ethers, Contract, ContractTransaction, Signer, BigNumber } from 'ethers'; +import { ethers, Contract, ContractTransaction, Signer, BigNumber, utils } from 'ethers'; +import assert from 'assert'; + +import { + abi as NFTD_ABI, + bytecode as NFTD_BYTECODE +} from '@uniswap/v3-periphery/artifacts/contracts/libraries/NFTDescriptor.sol/NFTDescriptor.json'; +import { + abi as NFTPD_ABI, + bytecode as NFTPD_BYTECODE, + linkReferences as NFTPD_LINKREFS +} from '@uniswap/v3-periphery/artifacts/contracts/NonfungibleTokenPositionDescriptor.sol/NonfungibleTokenPositionDescriptor.json'; +import { + abi as NFPM_ABI, + bytecode as NFPM_BYTECODE +} from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'; import { abi as TESTERC20_ABI, @@ -8,7 +23,12 @@ 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 +} from '../artifacts/test/contracts/WETH9.sol/WETH9.json'; +export { abi as NFPM_ABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'; export { abi as TESTERC20_ABI } from '../artifacts/test/contracts/TestERC20.sol/TestERC20.json'; export const TICK_MIN = -887272; @@ -57,3 +77,69 @@ export const initializePool = async (pool: Contract, sqrtPrice: string): Promise const transaction: ContractTransaction = await pool.initialize(BigNumber.from(sqrtPrice)); await transaction.wait(); }; + +export const deployWETH9Token = async (signer: Signer): Promise => { + const WETH9 = new ethers.ContractFactory(WETH9_ABI, WETH9_BYTECODE, signer); + const weth9 = await WETH9.deploy(); + + return weth9.address; +}; + +// https://github.com/ethers-io/ethers.js/issues/195 +const linkLibraries = ( + { + bytecode, + linkReferences + }: { + bytecode: string + linkReferences: { [fileName: string]: { [contractName: string]: { length: number; start: number }[] } } + }, + libraries: { [libraryName: string]: string }): string => { + Object.keys(linkReferences).forEach((fileName) => { + Object.keys(linkReferences[fileName]).forEach((contractName) => { + if (!Object.prototype.hasOwnProperty.call(libraries, contractName)) { + throw new Error(`Missing link library name ${contractName}`); + } + const address = utils.getAddress(libraries[contractName]).toLowerCase().slice(2); + linkReferences[fileName][contractName].forEach(({ start: byteStart, length: byteLength }) => { + const start = 2 + byteStart * 2; + const length = byteLength * 2; + bytecode = bytecode + .slice(0, start) + .concat(address) + .concat(bytecode.slice(start + length, bytecode.length)); + }); + }); + }); + return bytecode; +}; + +export const deployNFPM = async (signer: Signer, factory: Contract, weth9Address: string): Promise => { + // Deploy NonfungiblePositionManager. + // https://github.com/Uniswap/uniswap-v3-periphery/blob/main/test/shared/completeFixture.ts#L31 + const nftDescriptorLibraryFactory = new ethers.ContractFactory(NFTD_ABI, NFTD_BYTECODE, signer); + const nftDescriptorLibrary = await nftDescriptorLibraryFactory.deploy(); + assert(nftDescriptorLibrary.address, 'NFTDescriptorLibrary not deployed.'); + + // Linking NFTDescriptor library to NFTPD before deploying. + const linkedNFTPDBytecode = linkLibraries({ + bytecode: NFTPD_BYTECODE, + linkReferences: NFTPD_LINKREFS + }, { + NFTDescriptor: nftDescriptorLibrary.address + } + ); + + const positionDescriptorFactory = new ethers.ContractFactory( + NFTPD_ABI, + linkedNFTPDBytecode, + signer); + const nftDescriptor = await positionDescriptorFactory.deploy(weth9Address); + assert(nftDescriptor.address, 'NFTDescriptor not deployed.'); + + const positionManagerFactory = new ethers.ContractFactory( + NFPM_ABI, + NFPM_BYTECODE, + signer); + return await positionManagerFactory.deploy(factory.address, weth9Address, nftDescriptor.address); +}; diff --git a/packages/uni-watcher/test/contracts/WETH9.sol b/packages/util/test/contracts/WETH9.sol similarity index 100% rename from packages/uni-watcher/test/contracts/WETH9.sol rename to packages/util/test/contracts/WETH9.sol