Handle pool created event (#124)

* Update Token entities.

* Complete handlePoolCreated in events.

Co-authored-by: nabarun <nabarun@deepstacksoft.com>
This commit is contained in:
Ashwin Phatak 2021-07-07 11:10:10 +05:30 committed by GitHub
parent 194d079d5e
commit f7df56bb75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 170 additions and 55 deletions

View File

@ -22,7 +22,7 @@
subscribersDir = "src/subscriber" subscribersDir = "src/subscriber"
[upstream] [upstream]
gqlEndpoint = "http://127.0.0.1:8083/graphql" gqlEndpoint = "http://127.0.0.1:8082/graphql"
gqlSubscriptionEndpoint = "http://127.0.0.1:5000/graphql" gqlSubscriptionEndpoint = "http://127.0.0.1:5000/graphql"
[upstream.cache] [upstream.cache]

View File

@ -1,24 +1,19 @@
import { gql } from 'apollo-server-express'; import { gql } from '@apollo/client/core';
import { GraphQLClient } from '@vulcanize/ipld-eth-client'; import { GraphQLClient, GraphQLConfig } from '@vulcanize/ipld-eth-client';
import { querySymbol } from './queries'; import { queryName, queryDecimals, queryTotalSupply, querySymbol } from './queries';
interface Config {
gqlEndpoint: string;
gqlSubscriptionEndpoint: string;
}
export class Client { export class Client {
_config: Config; _config: GraphQLConfig;
_client: GraphQLClient; _client: GraphQLClient;
constructor (config: Config) { constructor (config: GraphQLConfig) {
this._config = config; this._config = config;
this._client = new GraphQLClient(config); this._client = new GraphQLClient(config);
} }
async getSymbol (blockHash: string | undefined, token: string): Promise<any> { async getSymbol (blockHash: string, token: string): Promise<any> {
const { symbol } = await this._client.query( const { symbol } = await this._client.query(
gql(querySymbol), gql(querySymbol),
{ blockHash, token } { blockHash, token }
@ -26,4 +21,31 @@ export class Client {
return symbol; return symbol;
} }
async getName (blockHash: string, token: string): Promise<any> {
const { name } = await this._client.query(
gql(queryName),
{ blockHash, token }
);
return name;
}
async getTotalSupply (blockHash: string, token: string): Promise<any> {
const { totalSupply } = await this._client.query(
gql(queryTotalSupply),
{ blockHash, token }
);
return totalSupply;
}
async getDecimals (blockHash: string, token: string): Promise<any> {
const { decimals } = await this._client.query(
gql(queryDecimals),
{ blockHash, token }
);
return decimals;
}
} }

View File

@ -4,11 +4,9 @@ import { Cache } from '@vulcanize/cache';
import ethQueries from './eth-queries'; import ethQueries from './eth-queries';
import { padKey } from './utils'; import { padKey } from './utils';
import { GraphQLClient } from './graphql-client'; import { GraphQLClient, GraphQLConfig } from './graphql-client';
interface Config { interface Config extends GraphQLConfig {
gqlEndpoint: string;
gqlSubscriptionEndpoint: string;
cache: Cache | undefined; cache: Cache | undefined;
} }

View File

@ -10,16 +10,16 @@ import { WebSocketLink } from '@apollo/client/link/ws';
const log = debug('vulcanize:client'); const log = debug('vulcanize:client');
interface Config { export interface GraphQLConfig {
gqlEndpoint: string; gqlEndpoint: string;
gqlSubscriptionEndpoint: string; gqlSubscriptionEndpoint: string;
} }
export class GraphQLClient { export class GraphQLClient {
_config: Config; _config: GraphQLConfig;
_client: ApolloClient<NormalizedCacheObject>; _client: ApolloClient<NormalizedCacheObject>;
constructor (config: Config) { constructor (config: GraphQLConfig) {
this._config = config; this._config = config;
const { gqlEndpoint, gqlSubscriptionEndpoint } = config; const { gqlEndpoint, gqlSubscriptionEndpoint } = config;

View File

@ -30,7 +30,19 @@ export class Database {
return this._conn.close(); return this._conn.close();
} }
async loadFactory ({ id, blockNumber }: DeepPartial<Factory>): Promise<Factory> { async getToken ({ id, blockNumber }: DeepPartial<Token>): Promise<Token | undefined> {
const repo = this._conn.getRepository(Token);
return repo.createQueryBuilder('token')
.where('id = :id AND block_number <= :blockNumber', {
id,
blockNumber
})
.orderBy('token.block_number', 'DESC')
.getOne();
}
async loadFactory ({ id, blockNumber, ...values }: DeepPartial<Factory>): Promise<Factory> {
return this._conn.transaction(async (tx) => { return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(Factory); const repo = tx.getRepository(Factory);
@ -39,10 +51,11 @@ export class Database {
id, id,
blockNumber blockNumber
}) })
.orderBy('factory.block_number', 'DESC')
.getOne(); .getOne();
if (!entity) { if (!entity) {
entity = repo.create({ blockNumber, id }); entity = repo.create({ blockNumber, id, ...values });
entity = await repo.save(entity); entity = await repo.save(entity);
} }
@ -50,7 +63,7 @@ export class Database {
}); });
} }
async loadPool ({ id, blockNumber }: DeepPartial<Pool>): Promise<Pool> { async loadPool ({ id, blockNumber, ...values }: DeepPartial<Pool>): Promise<Pool> {
return this._conn.transaction(async (tx) => { return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(Pool); const repo = tx.getRepository(Pool);
@ -59,10 +72,11 @@ export class Database {
id, id,
blockNumber blockNumber
}) })
.orderBy('pool.block_number', 'DESC')
.getOne(); .getOne();
if (!entity) { if (!entity) {
entity = repo.create({ blockNumber, id }); entity = repo.create({ blockNumber, id, ...values });
entity = await repo.save(entity); entity = await repo.save(entity);
} }
@ -70,7 +84,7 @@ export class Database {
}); });
} }
async loadToken ({ id, blockNumber }: DeepPartial<Token>, getValues: () => Promise<DeepPartial<Token>>): Promise<Token> { async loadToken ({ id, blockNumber, ...values }: DeepPartial<Token>): Promise<Token> {
return this._conn.transaction(async (tx) => { return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(Token); const repo = tx.getRepository(Token);
@ -79,11 +93,11 @@ export class Database {
id, id,
blockNumber blockNumber
}) })
.orderBy('token.block_number', 'DESC')
.getOne(); .getOne();
if (!entity) { if (!entity) {
const tokenValues = await getValues(); entity = repo.create({ blockNumber, id, ...values });
entity = repo.create({ blockNumber, id, ...tokenValues });
entity = await repo.save(entity); entity = await repo.save(entity);
} }
@ -91,6 +105,14 @@ export class Database {
}); });
} }
async saveFactory (factory: Factory, blockNumber: number): Promise<Factory> {
return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(Factory);
factory.blockNumber = blockNumber;
return repo.save(factory);
});
}
// Returns true if events have already been synced for the (block, token) combination. // Returns true if events have already been synced for the (block, token) combination.
async didSyncEvents ({ blockHash, token }: { blockHash: string, token: string }): Promise<boolean> { async didSyncEvents ({ blockHash, token }: { blockHash: string, token: string }): Promise<boolean> {
const numRows = await this._conn.getRepository(EventSyncProgress) const numRows = await this._conn.getRepository(EventSyncProgress)

View File

@ -6,9 +6,11 @@ export class Factory {
@PrimaryColumn('varchar', { length: 42 }) @PrimaryColumn('varchar', { length: 42 })
id!: string; id!: string;
@Column('numeric') @PrimaryColumn('numeric')
blockNumber!: number; blockNumber!: number;
@Column('numeric', { default: 0 }) @Column('numeric', { default: BigInt(0) })
poolCount!: number; poolCount!: bigint;
// TODO: Add remaining fields when they are used.
} }

View File

@ -1,4 +1,6 @@
import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; import { Entity, PrimaryColumn, Column, Index, ManyToOne } from 'typeorm';
import { Token } from './Token';
@Entity() @Entity()
@Index(['blockNumber', 'id']) @Index(['blockNumber', 'id'])
@ -6,6 +8,17 @@ export class Pool {
@PrimaryColumn('varchar', { length: 42 }) @PrimaryColumn('varchar', { length: 42 })
id!: string; id!: string;
@Column('numeric') @PrimaryColumn('numeric')
blockNumber!: number; blockNumber!: number;
@ManyToOne(() => Token)
token0!: Token;
@ManyToOne(() => Token)
token1!: Token;
@Column('numeric')
feeTier!: bigint
// TODO: Add remaining fields when they are used.
} }

View File

@ -6,9 +6,17 @@ export class Token {
@PrimaryColumn('varchar', { length: 42 }) @PrimaryColumn('varchar', { length: 42 })
id!: string; id!: string;
@Column('numeric') @PrimaryColumn('numeric')
blockNumber!: number; blockNumber!: number;
@Column('varchar') @Column('varchar')
symbol!: string; symbol!: string;
@Column('varchar')
name!: string;
@Column('numeric')
totalSupply!: number;
// TODO: Add remaining fields when they are used.
} }

View File

@ -2,6 +2,7 @@ import assert from 'assert';
import debug from 'debug'; import debug from 'debug';
import { Client as UniClient } from '@vulcanize/uni-watcher'; import { Client as UniClient } from '@vulcanize/uni-watcher';
import { Client as ERC20Client } from '@vulcanize/erc20-watcher'; import { Client as ERC20Client } from '@vulcanize/erc20-watcher';
import { BigNumber } from 'ethers';
import { Database } from './database'; import { Database } from './database';
@ -58,6 +59,7 @@ export class EventWatcher {
switch (eventType) { switch (eventType) {
case 'PoolCreatedEvent': case 'PoolCreatedEvent':
log('PoolCreated event', contract);
this._handlePoolCreated(blockHash, blockNumber, contract, eventValues as PoolCreatedEvent); this._handlePoolCreated(blockHash, blockNumber, contract, eventValues as PoolCreatedEvent);
break; break;
@ -67,28 +69,62 @@ export class EventWatcher {
} }
async _handlePoolCreated (blockHash: string, blockNumber: number, contractAddress: string, poolCreatedEvent: PoolCreatedEvent): Promise<void> { async _handlePoolCreated (blockHash: string, blockNumber: number, contractAddress: string, poolCreatedEvent: PoolCreatedEvent): Promise<void> {
const { token0: token0Address, token1: token1Address, fee, tickSpacing, pool: poolAddress } = poolCreatedEvent; const { token0: token0Address, token1: token1Address, fee, pool: poolAddress } = poolCreatedEvent;
// Load factory. // Load factory.
const factory = await this._db.loadFactory({ blockNumber, id: contractAddress }); const factory = await this._db.loadFactory({ blockNumber, id: contractAddress });
factory.poolCount = factory.poolCount + 1;
// Create new Pool entity. // Update Factory.
const pool = this._db.loadPool({ blockNumber, id: poolAddress }); let factoryPoolCount = BigNumber.from(factory.poolCount);
factoryPoolCount = factoryPoolCount.add(1);
factory.poolCount = BigInt(factoryPoolCount.toHexString());
// TODO: Load Token entities. // Get Tokens.
const getTokenValues = async (tokenAddress: string) => { let [token0, token1] = await Promise.all([
this._db.getToken({ blockNumber, id: token0Address }),
this._db.getToken({ blockNumber, id: token1Address })
]);
// Create Token.
const createToken = async (tokenAddress: string) => {
const { value: symbol } = await this._erc20Client.getSymbol(blockHash, tokenAddress); const { value: symbol } = await this._erc20Client.getSymbol(blockHash, tokenAddress);
return { symbol }; const { value: name } = await this._erc20Client.getName(blockHash, tokenAddress);
const { value: totalSupply } = await this._erc20Client.getTotalSupply(blockHash, tokenAddress);
// TODO: decimals not implemented by erc20-watcher.
// const { value: decimals } = await this._erc20Client.getDecimals(blockHash, tokenAddress);
return this._db.loadToken({
blockNumber,
id: token1Address,
symbol,
name,
totalSupply
});
}; };
const token0 = this._db.loadToken({ blockNumber, id: token0Address }, () => getTokenValues(token0Address)); // Create Tokens if not present.
const token1 = this._db.loadToken({ blockNumber, id: token1Address }, () => getTokenValues(token1Address)); if (!token0) {
token0 = await createToken(token0Address);
}
// TODO: Update Token entities. if (!token1) {
token1 = await createToken(token1Address);
}
// TODO: Update Pool entity. // Create new Pool entity.
// Skipping adding createdAtTimestamp field as it is not queried in frontend subgraph.
await this._db.loadPool({
blockNumber,
id: poolAddress,
token0: token0,
token1: token1,
feeTier: BigInt(fee)
});
// TODO: Save entities to DB. // Skipping updating token whitelistPools field as it is not queried in frontend subgraph.
// Save entities to DB.
await this._db.saveFactory(factory, blockNumber);
} }
} }

View File

@ -11,6 +11,24 @@ Start watching the factory contract:
Example: Example:
```bash ```bash
$ npx ts-node src/cli/watch-contract.ts --configFile environments/local.toml --address 0xfE0034a874c2707c23F91D7409E9036F5e08ac34 --kind factory --startingBlock 100 $ yarn watch:contract --address 0xfE0034a874c2707c23F91D7409E9036F5e08ac34 --kind factory --startingBlock 100
``` ```
## Scripts
* `yarn server`
Start the GraphQL server.
* `yarn watch:contract`
Add contract to watch.
* `yarn lint`
Lint files.
```bash
# Lint fix.
$ yarn lint --fix
```

View File

@ -9,7 +9,8 @@
"server:mock": "MOCK=1 nodemon src/server.ts -f environments/local.toml", "server:mock": "MOCK=1 nodemon src/server.ts -f environments/local.toml",
"test": "mocha -r ts-node/register src/**/*.spec.ts", "test": "mocha -r ts-node/register src/**/*.spec.ts",
"lint": "eslint .", "lint": "eslint .",
"build": "tsc" "build": "tsc",
"watch:contract": "ts-node src/cli/watch-contract.ts --configFile environments/local.toml"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,16 +1,11 @@
import { gql } from 'apollo-server-express'; import { gql } from '@apollo/client/core';
import { GraphQLClient } from '@vulcanize/ipld-eth-client'; import { GraphQLClient, GraphQLConfig } from '@vulcanize/ipld-eth-client';
interface Config {
gqlEndpoint: string;
gqlSubscriptionEndpoint: string;
}
export class Client { export class Client {
_config: Config; _config: GraphQLConfig;
_client: GraphQLClient; _client: GraphQLClient;
constructor (config: Config) { constructor (config: GraphQLConfig) {
this._config = config; this._config = config;
this._client = new GraphQLClient(config); this._client = new GraphQLClient(config);