Generating eth_call based lazy watcher (#249)

* Add entity generation.

* Add resolvers generation.

* Add queries in resolvers generation.

* Add indexer generation.

* Extract helper code in utils.

* Add server and artifacts generation.

* Fix solidity-flattener issue.

* Update readme and cleanup misc files.

* Add queries to entity generation.

* Add database generation.

* Use snakecase in database.

* Add readme generation.

* Change template file names.

* Add method descriptions.

* Change mode to eth_call in readme.

Co-authored-by: prathamesh <prathamesh.musale0@gmail.com>
This commit is contained in:
Ashwin Phatak 2021-09-23 16:55:46 +05:30 committed by GitHub
parent 4f8f1d8cd7
commit 92b7967895
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1388 additions and 125 deletions

View File

@ -10,14 +10,15 @@
## Run
* Run the following command to generate schema from a contract file:
* Run the following command to generate a watcher from a contract file:
```bash
yarn codegen:gql --input-file <input-file-path> --output-file [output-file-path] --mode [eth_call | storage] --flatten [true | false]
yarn codegen --input-file <input-file-path> --contract-name <contract-name> --output-folder [output-folder] --mode [eth_call | storage] --flatten [true | false]
```
* `input-file`(alias: `i`): Input contract file path or an URL (required).
* `output-file`(alias: `o`): Schema output file path (logs output using `stdout` if not provided).
* `contract-name`(alias: `c`): Main contract name (required).
* `output-folder`(alias: `o`): Output folder path. (logs output using `stdout` if not provided).
* `mode`(alias: `m`): Code generation mode (default: `storage`).
* `flatten`(alias: `f`): Flatten the input contract file (default: `true`).
@ -26,11 +27,11 @@
Examples:
```bash
yarn codegen:gql --input-file ./test/examples/contracts/ERC20.sol --output-file ./ERC20-schema.gql --mode eth_call
yarn codegen --input-file ./test/examples/contracts/ERC20.sol --contract-name ERC20 --output-folder ../my-erc20-watcher --mode eth_call
```
```bash
yarn codegen:gql --input-file https://git.io/Jupci --output-file ./ERC721-schema.gql --mode storage
yarn codegen --input-file https://git.io/Jupci --contract-name ERC721 --output-folder ../my-erc721-watcher --mode eth_call
```
## Demo
@ -41,16 +42,18 @@
yarn
```
* Generate schema from a contract file:
* Generate a watcher from a contract file:
```bash
yarn codegen:gql --input-file ../../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol --output-file ./ERC20-schema.gql --mode storage
yarn codegen --input-file ../../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol --contract-name ERC20 --output-folder ../demo-erc20-watcher --mode eth_call
```
* Generate schema from a flattened contract file from an URL:
This will create a folder called `demo-erc20-watcher` containing the generated code at the specified path. Follow the steps in `demo-erc20-watcher/README.md` to setup and run the generated watcher.
* Generate a watcher from a flattened contract file from an URL:
```bash
yarn codegen:gql --input-file https://git.io/Jupci --output-file ./ERC721-schema.gql --mode eth_call
yarn codegen --input-file https://git.io/Jupci --contract-name ERC721 --output-folder ../demo-erc721-watcher --mode eth_call
```
## References

View File

@ -6,7 +6,7 @@
"main": "index.js",
"scripts": {
"lint": "eslint .",
"codegen:gql": "ts-node src/generate-schema.ts"
"codegen": "ts-node src/generate-code.ts"
},
"repository": {
"type": "git",
@ -23,7 +23,9 @@
"@solidity-parser/parser": "^0.13.2",
"graphql": "^15.5.0",
"graphql-compose": "^9.0.3",
"handlebars": "^4.7.7",
"node-fetch": "^2",
"solc": "^0.8.7-fixed",
"ts-node": "^10.2.1",
"typescript": "^4.3.2",
"yargs": "^17.1.1"

View File

@ -0,0 +1,35 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import solc from 'solc';
import { Writable } from 'stream';
/**
* Compiles the given contract using solc and writes the resultant artifacts to a file.
* @param outStream A writable output stream to write the artifacts file to.
* @param contractContent Contents of the contract file to be compiled.
* @param contractFileName Input contract file name.
* @param contractName Name of the main contract in the contract file.
*/
export function exportArtifacts (outStream: Writable, contractContent: string, contractFileName: string, contractName: string): void {
const input: any = {
language: 'Solidity',
sources: {},
settings: {
outputSelection: {
'*': {
'*': ['abi', 'storageLayout']
}
}
}
};
input.sources[contractFileName] = {
content: contractContent
};
// Get artifacts for the required contract.
const output = JSON.parse(solc.compile(JSON.stringify(input))).contracts[contractFileName][contractName];
outStream.write(JSON.stringify(output, null, 2));
}

View File

@ -0,0 +1,24 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import fs from 'fs';
import path from 'path';
import Handlebars from 'handlebars';
import { Writable } from 'stream';
const TEMPLATE_FILE = './templates/config-template.handlebars';
/**
* Writes the config file generated from a template to a stream.
* @param folderName Watcher folder name to be passed to the template.
* @param outStream A writable output stream to write the config file to.
*/
export function exportConfig (folderName: string, outStream: Writable): void {
const templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
const template = Handlebars.compile(templateString);
const config = template({
folderName
});
outStream.write(config);
}

View File

@ -0,0 +1,73 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import fs from 'fs';
import path from 'path';
import assert from 'assert';
import Handlebars from 'handlebars';
import { Writable } from 'stream';
import _ from 'lodash';
import { getTsForSol } from './utils/type-mappings';
import { Param } from './utils/types';
import { capitalizeHelper } from './utils/handlebar-helpers';
const TEMPLATE_FILE = './templates/database-template.handlebars';
export class Database {
_queries: Array<any>;
_templateString: string;
constructor () {
this._queries = [];
this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
Handlebars.registerHelper('capitalize', capitalizeHelper);
}
/**
* Stores the query to be passed to the template.
* @param name Name of the query.
* @param params Parameters to the query.
* @param returnType Return type for the query.
*/
addQuery (name: string, params: Array<Param>, returnType: string): void {
// Check if the query is already added.
if (this._queries.some(query => query.name === name)) {
return;
}
const queryObject = {
name: name,
params: _.cloneDeep(params),
returnType: returnType
};
queryObject.params = queryObject.params.map((param) => {
const tsParamType = getTsForSol(param.type);
assert(tsParamType);
param.type = tsParamType;
return param;
});
const tsReturnType = getTsForSol(returnType);
assert(tsReturnType);
queryObject.returnType = tsReturnType;
this._queries.push(queryObject);
}
/**
* Writes the database file generated from a template to a stream.
* @param outStream A writable output stream to write the database file to.
*/
exportDatabase (outStream: Writable): void {
const template = Handlebars.compile(this._templateString);
const obj = {
queries: this._queries
};
const database = template(obj);
outStream.write(database);
}
}

View File

@ -0,0 +1,97 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import fs from 'fs';
import path from 'path';
import assert from 'assert';
import Handlebars from 'handlebars';
import { Writable } from 'stream';
import { getTsForSol, getPgForTs } from './utils/type-mappings';
import { Param } from './utils/types';
const TEMPLATE_FILE = './templates/entity-template.handlebars';
export class Entity {
_entities: Array<any>;
_templateString: string;
constructor () {
this._entities = [];
this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
}
/**
* Creates an entity object from the query and stores to be passed to the template.
* @param name Name of the query.
* @param params Parameters to the query.
* @param returnType Return type for the query.
*/
addQuery (name: string, params: Array<Param>, returnType: string): void {
// Check if the query is already added.
if (this._entities.some(entity => entity.className.toLowerCase() === name.toLowerCase())) {
return;
}
const entityObject: any = {
// Capitalize the first letter of name.
className: `${name.charAt(0).toUpperCase()}${name.slice(1)}`,
indexOn: {},
columns: [{}],
returnType: returnType
};
entityObject.indexOn.columns = params.map((param) => {
return param.name;
});
entityObject.indexOn.unique = true;
entityObject.columns = params.map((param) => {
const length = param.type === 'address' ? 42 : null;
const name = param.name;
const tsType = getTsForSol(param.type);
assert(tsType);
const pgType = getPgForTs(tsType);
assert(pgType);
return {
name,
pgType,
tsType,
length
};
});
const tsReturnType = getTsForSol(returnType);
assert(tsReturnType);
const pgReturnType = getPgForTs(tsReturnType);
assert(pgReturnType);
entityObject.columns.push({
name: 'value',
pgType: pgReturnType,
tsType: tsReturnType
});
this._entities.push(entityObject);
}
/**
* Writes the generated entity files in the given directory.
* @param entityDir Directory to write the entities to.
*/
exportEntities (entityDir: string): void {
const template = Handlebars.compile(this._templateString);
this._entities.forEach(entityObj => {
const entity = template(entityObj);
const outStream: Writable = entityDir
? fs.createWriteStream(path.join(entityDir, `${entityObj.className}.ts`))
: process.stdout;
outStream.write(entity);
});
}
}

View File

@ -0,0 +1,179 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import fs from 'fs';
import fetch from 'node-fetch';
import path from 'path';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { flatten } from '@poanet/solidity-flattener';
import { parse, visit } from '@solidity-parser/parser';
import { Visitor } from './visitor';
import { exportServer } from './server';
import { exportConfig } from './config';
import { exportArtifacts } from './artifacts';
import { exportPackage } from './package';
import { exportTSConfig } from './tsconfig';
import { exportReadme } from './readme';
const MODE_ETH_CALL = 'eth_call';
const MODE_STORAGE = 'storage';
const main = async (): Promise<void> => {
const argv = await yargs(hideBin(process.argv))
.option('input-file', {
alias: 'i',
demandOption: true,
describe: 'Input contract file path or an url.',
type: 'string'
})
.option('contract-name', {
alias: 'c',
demandOption: true,
describe: 'Main contract name.',
type: 'string'
})
.option('output-folder', {
alias: 'o',
describe: 'Output folder path.',
type: 'string'
})
.option('mode', {
alias: 'm',
describe: 'Code generation mode.',
type: 'string',
default: MODE_STORAGE,
choices: [MODE_ETH_CALL, MODE_STORAGE]
})
.option('flatten', {
alias: 'f',
describe: 'Flatten the input contract file.',
type: 'boolean',
default: true
})
.argv;
let data: string;
if (argv['input-file'].startsWith('http')) {
// Assume flattened file in case of URL.
const response = await fetch(argv['input-file']);
data = await response.text();
} else {
data = argv.flatten
? await flatten(path.resolve(argv['input-file']))
: fs.readFileSync(path.resolve(argv['input-file'])).toString();
}
const visitor = new Visitor();
parseAndVisit(data, visitor, argv.mode);
generateWatcher(data, visitor, argv);
};
function parseAndVisit (data: string, visitor: Visitor, mode: string) {
// Get the abstract syntax tree for the flattened contract.
const ast = parse(data);
// Filter out library nodes.
ast.children = ast.children.filter(child => !(child.type === 'ContractDefinition' && child.kind === 'library'));
if (mode === MODE_ETH_CALL) {
visit(ast, {
FunctionDefinition: visitor.functionDefinitionVisitor.bind(visitor),
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
});
} else {
visit(ast, {
StateVariableDeclaration: visitor.stateVariableDeclarationVisitor.bind(visitor),
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
});
}
}
function generateWatcher (data: string, visitor: Visitor, argv: any) {
// Prepare directory structure for the watcher.
let outputDir = '';
if (argv['output-folder']) {
outputDir = path.resolve(argv['output-folder']);
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
const environmentsFolder = path.join(outputDir, 'environments');
if (!fs.existsSync(environmentsFolder)) fs.mkdirSync(environmentsFolder);
const artifactsFolder = path.join(outputDir, 'src/artifacts');
if (!fs.existsSync(artifactsFolder)) fs.mkdirSync(artifactsFolder, { recursive: true });
const entitiesFolder = path.join(outputDir, 'src/entity');
if (!fs.existsSync(entitiesFolder)) fs.mkdirSync(entitiesFolder, { recursive: true });
}
const inputFileName = path.basename(argv['input-file'], '.sol');
let outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/schema.gql'))
: process.stdout;
visitor.exportSchema(outStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/resolvers.ts'))
: process.stdout;
visitor.exportResolvers(outStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/indexer.ts'))
: process.stdout;
visitor.exportIndexer(outStream, inputFileName);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/server.ts'))
: process.stdout;
exportServer(outStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'environments/local.toml'))
: process.stdout;
exportConfig(path.basename(outputDir), outStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/artifacts/', `${inputFileName}.json`))
: process.stdout;
exportArtifacts(
outStream,
data,
`${inputFileName}.sol`,
argv['contract-name']
);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/database.ts'))
: process.stdout;
visitor.exportDatabase(outStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'package.json'))
: process.stdout;
exportPackage(path.basename(outputDir), outStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'tsconfig.json'))
: process.stdout;
exportTSConfig(outStream);
const entityDir = outputDir
? path.join(outputDir, 'src/entity')
: '';
visitor.exportEntities(entityDir);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'README.md'))
: process.stdout;
exportReadme(path.basename(outputDir), argv['contract-name'], outStream);
}
main().catch(err => {
console.error(err);
});

View File

@ -1,86 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { readFileSync, createWriteStream } from 'fs';
import fetch from 'node-fetch';
import path from 'path';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { flatten } from '@poanet/solidity-flattener';
import { parse, visit } from '@solidity-parser/parser';
import { Visitor } from './visitor';
const MODE_ETH_CALL = 'eth_call';
const MODE_STORAGE = 'storage';
const main = async (): Promise<void> => {
const argv = await yargs(hideBin(process.argv))
.option('input-file', {
alias: 'i',
demandOption: true,
describe: 'Input contract file path or an url.',
type: 'string'
})
.option('output-file', {
alias: 'o',
describe: 'Schema output file path.',
type: 'string'
})
.option('mode', {
alias: 'm',
describe: 'Code generation mode.',
type: 'string',
default: MODE_STORAGE,
choices: [MODE_ETH_CALL, MODE_STORAGE]
})
.option('flatten', {
alias: 'f',
describe: 'Flatten the input contract file.',
type: 'boolean',
default: true
})
.argv;
let data: string;
if (argv['input-file'].startsWith('http')) {
// Assume flattened file in case of URL.
const response = await fetch(argv['input-file']);
data = await response.text();
} else {
data = argv.flatten
? await flatten(path.resolve(argv['input-file']))
: readFileSync(path.resolve(argv['input-file'])).toString();
}
// Get the abstract syntax tree for the flattened contract.
const ast = parse(data);
// Filter out library nodes.
ast.children = ast.children.filter(child => !(child.type === 'ContractDefinition' && child.kind === 'library'));
const visitor = new Visitor();
if (argv.mode === MODE_ETH_CALL) {
visit(ast, {
FunctionDefinition: visitor.functionDefinitionVisitor.bind(visitor),
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
});
} else {
visit(ast, {
StateVariableDeclaration: visitor.stateVariableDeclarationVisitor.bind(visitor),
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
});
}
const outStream = argv['output-file']
? createWriteStream(path.resolve(argv['output-file']))
: process.stdout;
visitor.exportSchema(outStream);
};
main().catch(err => {
console.error(err);
});

View File

@ -0,0 +1,76 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import fs from 'fs';
import path from 'path';
import assert from 'assert';
import Handlebars from 'handlebars';
import { Writable } from 'stream';
import _ from 'lodash';
import { getTsForSol } from './utils/type-mappings';
import { Param } from './utils/types';
import { compareHelper, capitalizeHelper } from './utils/handlebar-helpers';
const TEMPLATE_FILE = './templates/indexer-template.handlebars';
export class Indexer {
_queries: Array<any>;
_templateString: string;
constructor () {
this._queries = [];
this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
Handlebars.registerHelper('compare', compareHelper);
Handlebars.registerHelper('capitalize', capitalizeHelper);
}
/**
* Stores the query to be passed to the template.
* @param name Name of the query.
* @param params Parameters to the query.
* @param returnType Return type for the query.
*/
addQuery (name: string, params: Array<Param>, returnType: string): void {
// Check if the query is already added.
if (this._queries.some(query => query.name === name)) {
return;
}
const queryObject = {
name: name,
params: _.cloneDeep(params),
returnType: returnType
};
queryObject.params = queryObject.params.map((param) => {
const tsParamType = getTsForSol(param.type);
assert(tsParamType);
param.type = tsParamType;
return param;
});
const tsReturnType = getTsForSol(returnType);
assert(tsReturnType);
queryObject.returnType = tsReturnType;
this._queries.push(queryObject);
}
/**
* Writes the indexer file generated from a template to a stream.
* @param outStream A writable output stream to write the indexer file to.
* @param inputFileName Input contract file name to be passed to the template.
*/
exportIndexer (outStream: Writable, inputFileName: string): void {
const template = Handlebars.compile(this._templateString);
const obj = {
inputFileName,
queries: this._queries
};
const indexer = template(obj);
outStream.write(indexer);
}
}

View File

@ -0,0 +1,24 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import fs from 'fs';
import path from 'path';
import Handlebars from 'handlebars';
import { Writable } from 'stream';
const TEMPLATE_FILE = './templates/package-template.handlebars';
/**
* Writes the package.json file generated from a template to a stream.
* @param folderName Watcher folder name to be passed to the template.
* @param outStream A writable output stream to write the package.json file to.
*/
export function exportPackage (folderName: string, outStream: Writable): void {
const templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
const template = Handlebars.compile(templateString);
const packageString = template({
folderName
});
outStream.write(packageString);
}

View File

@ -0,0 +1,26 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import fs from 'fs';
import path from 'path';
import Handlebars from 'handlebars';
import { Writable } from 'stream';
const TEMPLATE_FILE = './templates/readme-template.handlebars';
/**
* Writes the README.md file generated from a template to a stream.
* @param folderName Watcher folder name to be passed to the template.
* @param contractName Input contract name given as title of the README.
* @param outStream A writable output stream to write the README.md file to.
*/
export function exportReadme (folderName: string, contractName: string, outStream: Writable): void {
const templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
const template = Handlebars.compile(templateString);
const readmeString = template({
folderName,
contractName
});
outStream.write(readmeString);
}

View File

@ -0,0 +1,66 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import fs from 'fs';
import path from 'path';
import { Writable } from 'stream';
import Handlebars from 'handlebars';
import assert from 'assert';
import _ from 'lodash';
import { getTsForSol } from './utils/type-mappings';
import { Param } from './utils/types';
const TEMPLATE_FILE = './templates/resolvers-template.handlebars';
export class Resolvers {
_queries: Array<any>;
_templateString: string;
constructor () {
this._queries = [];
this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
}
/**
* Stores the query to be passed to the template.
* @param name Name of the query.
* @param params Parameters to the query.
* @param returnType Return type for the query.
*/
addQuery (name: string, params: Array<Param>, returnType: string): void {
// Check if the query is already added.
if (this._queries.some(query => query.name === name)) {
return;
}
const queryObject = {
name: name,
params: _.cloneDeep(params),
returnType: returnType
};
queryObject.params = queryObject.params.map((param) => {
const tsParamType = getTsForSol(param.type);
assert(tsParamType);
param.type = tsParamType;
return param;
});
this._queries.push(queryObject);
}
/**
* Writes the resolvers file generated from a template to a stream.
* @param outStream A writable output stream to write the resolvers file to.
*/
exportResolvers (outStream: Writable): void {
const template = Handlebars.compile(this._templateString);
const obj = {
queries: this._queries
};
const resolvers = template(obj);
outStream.write(resolvers);
}
}

View File

@ -2,23 +2,20 @@
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import { GraphQLSchema, printSchema } from 'graphql';
import { SchemaComposer } from 'graphql-compose';
import { Writable } from 'stream';
export interface Param {
name: string;
type: string;
}
import { getTsForSol, getGqlForTs } from './utils/type-mappings';
import { Param } from './utils/types';
export class Schema {
_composer: SchemaComposer;
_typeMapping: Map<string, string>;
_events: Array<string>;
constructor () {
this._composer = new SchemaComposer();
this._typeMapping = new Map();
this._events = [];
this._addBasicTypes();
@ -32,10 +29,13 @@ export class Schema {
*/
addQuery (name: string, params: Array<Param>, returnType: string): void {
// TODO: Handle cases where returnType/params type is an array.
const tsReturnType = getTsForSol(returnType);
assert(tsReturnType);
const queryObject: { [key: string]: any; } = {};
queryObject[name] = {
// Get type composer object for return type from the schema composer.
type: this._composer.getOTC(`Result${this._typeMapping.get(returnType)}`).NonNull,
type: this._composer.getOTC(`Result${getGqlForTs(tsReturnType)}`).NonNull,
args: {
blockHash: 'String!',
contractAddress: 'String!'
@ -44,7 +44,9 @@ export class Schema {
if (params.length > 0) {
queryObject[name].args = params.reduce((acc, curr) => {
acc[curr.name] = this._typeMapping.get(curr.type) + '!';
const tsCurrType = getTsForSol(curr.type);
assert(tsCurrType);
acc[curr.name] = `${getGqlForTs(tsCurrType)}!`;
return acc;
}, queryObject[name].args);
}
@ -67,7 +69,9 @@ export class Schema {
if (params.length > 0) {
typeObject.fields = params.reduce((acc, curr) => {
acc[curr.name] = this._typeMapping.get(curr.type) + '!';
const tsCurrType = getTsForSol(curr.type);
assert(tsCurrType);
acc[curr.name] = `${getGqlForTs(tsCurrType)}!`;
return acc;
}, typeObject.fields);
}
@ -99,8 +103,8 @@ export class Schema {
*/
exportSchema (outStream: Writable): void {
// Get schema as a string from GraphQLSchema.
const schema = printSchema(this.buildSchema());
outStream.write(schema);
const schemaString = printSchema(this.buildSchema());
outStream.write(schemaString);
}
/**
@ -152,14 +156,6 @@ export class Schema {
proof: () => this._composer.getOTC('Proof')
}
});
// TODO Get typemapping from ethersjs.
this._typeMapping.set('string', 'String');
this._typeMapping.set('uint8', 'Int');
this._typeMapping.set('uint256', 'BigInt');
this._typeMapping.set('address', 'String');
this._typeMapping.set('bool', 'Boolean');
this._typeMapping.set('bytes4', 'String');
}
/**

View File

@ -0,0 +1,21 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import fs from 'fs';
import path from 'path';
import Handlebars from 'handlebars';
import { Writable } from 'stream';
const TEMPLATE_FILE = './templates/server-template.handlebars';
/**
* Writes the server file generated from a template to a stream.
* @param outStream A writable output stream to write the server file to.
*/
export function exportServer (outStream: Writable): void {
const templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
const template = Handlebars.compile(templateString);
const server = template({});
outStream.write(server);
}

View File

@ -0,0 +1,25 @@
[server]
host = "127.0.0.1"
port = 3008
mode = "eth_call"
[database]
type = "postgres"
host = "localhost"
port = 5432
database = "{{folderName}}"
username = "postgres"
password = "postgres"
synchronize = true
logging = false
[upstream]
[upstream.ethServer]
gqlApiEndpoint = "http://127.0.0.1:8082/graphql"
gqlPostgraphileEndpoint = "http://127.0.0.1:5000/graphql"
rpcProviderEndpoint = "http://127.0.0.1:8081"
[upstream.cache]
name = "requests"
enabled = false
deleteOnStart = false

View File

@ -0,0 +1,72 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import { Connection, ConnectionOptions, DeepPartial } from 'typeorm';
import path from 'path';
import { Database as BaseDatabase } from '@vulcanize/util';
{{#each queries as | query |}}
import { {{capitalize query.name tillIndex=1}} } from './entity/{{capitalize query.name tillIndex=1}}';
{{/each}}
export class Database {
_config: ConnectionOptions
_conn!: Connection
_baseDatabase: BaseDatabase;
constructor (config: ConnectionOptions) {
assert(config);
this._config = {
...config,
entities: [path.join(__dirname, 'entity/*')]
};
this._baseDatabase = new BaseDatabase(this._config);
}
async init (): Promise<void> {
this._conn = await this._baseDatabase.init();
}
async close (): Promise<void> {
return this._baseDatabase.close();
}
{{#each queries as | query |}}
async get{{capitalize query.name tillIndex=1}} ({ blockHash, contractAddress
{{~#each query.params}}, {{this.name~}} {{/each}} }: { blockHash: string, contractAddress: string
{{~#each query.params}}, {{this.name~}}: {{this.type~}} {{/each}} }): Promise<{{capitalize query.name tillIndex=1}} | undefined> {
return this._conn.getRepository({{capitalize query.name tillIndex=1}})
.createQueryBuilder('{{query.name}}')
.where(`${this._getColumn('{{capitalize query.name tillIndex=1}}', 'blockHash')} = :blockHash AND ${this._getColumn('{{capitalize query.name tillIndex=1}}', 'contractAddress')} = :contractAddress
{{~#each query.params}} AND ${this._getColumn('{{capitalize query.name tillIndex=1}}', '{{this.name}}')} = :{{this.name~}} {{/each}}`, {
blockHash,
contractAddress
{{~#each query.params}},
{{this.name}}
{{~/each}}
})
.getOne();
}
{{/each}}
{{~#each queries as | query |}}
async save{{capitalize query.name tillIndex=1}} ({ blockHash, contractAddress
{{~#each query.params}}, {{this.name~}} {{/each}}, value, proof}: DeepPartial<{{capitalize query.name tillIndex=1}}>): Promise<{{capitalize query.name tillIndex=1}}> {
const repo = this._conn.getRepository({{capitalize query.name tillIndex=1}});
const entity = repo.create({ blockHash, contractAddress
{{~#each query.params}}, {{this.name~}} {{/each}}, value, proof });
return repo.save(entity);
}
{{/each}}
_getColumn (entityName: string, propertyName: string) {
return this._conn.getMetadata(entityName).findColumnWithPropertyName(propertyName)?.databaseName
}
}

View File

@ -0,0 +1,30 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
@Entity()
{{#if indexOn.columns}}
@Index(['blockHash', 'contractAddress'
{{~#each indexOn.columns}}, '{{this}}'
{{~/each}}], { unique: {{indexOn.unique}} })
{{/if}}
export class {{className}} {
@PrimaryGeneratedColumn()
id!: number;
@Column('varchar', { length: 66 })
blockHash!: string;
@Column('varchar', { length: 42 })
contractAddress!: string;
{{#each columns as | column |}}
@Column('{{column.pgType}}' {{~#if column.length}}, { length: {{column.length}} } {{~/if}})
{{column.name}}!: {{column.tsType}};
{{/each}}
@Column('text', { nullable: true })
proof!: string;
}

View File

@ -0,0 +1,98 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import debug from 'debug';
import { JsonFragment } from '@ethersproject/abi';
import JSONbig from 'json-bigint';
import { BigNumber, ethers, Contract } from 'ethers';
import { BaseProvider } from '@ethersproject/providers';
import { EthClient } from '@vulcanize/ipld-eth-client';
import { StorageLayout } from '@vulcanize/solidity-mapper';
import { ValueResult } from '@vulcanize/util';
import { Database } from './database';
import artifacts from './artifacts/{{inputFileName}}.json';
const log = debug('vulcanize:indexer');
export class Indexer {
_db: Database
_ethClient: EthClient
_ethProvider: BaseProvider
_abi: JsonFragment[]
_storageLayout: StorageLayout
_contract: ethers.utils.Interface
_serverMode: string
constructor (db: Database, ethClient: EthClient, ethProvider: BaseProvider, serverMode: string) {
assert(db);
assert(ethClient);
this._db = db;
this._ethClient = ethClient;
this._ethProvider = ethProvider;
this._serverMode = serverMode;
const { abi, storageLayout } = artifacts;
assert(abi);
assert(storageLayout);
this._abi = abi;
this._storageLayout = storageLayout;
this._contract = new ethers.utils.Interface(this._abi);
}
{{#each queries as | query |}}
async {{query.name}} (blockHash: string, contractAddress: string
{{~#each query.params}}, {{this.name~}}: {{this.type~}} {{/each}}): Promise<ValueResult> {
const entity = await this._db.get{{capitalize query.name tillIndex=1}}({ blockHash, contractAddress
{{~#each query.params}}, {{this.name~}} {{~/each}} });
if (entity) {
log('{{query.name}}: db hit.');
return {
value: entity.value,
proof: JSON.parse(entity.proof)
};
}
log('{{query.name}}: db miss, fetching from upstream server');
const contract = new Contract(contractAddress, this._abi, this._ethProvider);
let value = null;
{{~#if query.params}}
const { block: { number } } = await this._ethClient.getBlockByHash(blockHash);
const blockNumber = BigNumber.from(number).toNumber();
value = await contract.{{query.name}}(
{{~#each query.params}}{{this.name}}, {{/each}}{ blockTag: blockNumber });
{{else}}
value = await contract.{{query.name}}();
{{/if}}
{{~#if (compare query.returnType 'bigint')}}
value = value.toString();
value = BigInt(value);
{{/if}}
const result: ValueResult = { value };
const { proof } = result;
await this._db.save{{capitalize query.name tillIndex=1}}({ blockHash, contractAddress
{{~#each query.params}}, {{this.name~}} {{/each}}, value, proof: JSONbig.stringify(proof) });
return result;
}
{{#unless @last}}
{{/unless}}
{{/each}}
}

View File

@ -0,0 +1,44 @@
{
"name": "@vulcanize/{{folderName}}",
"version": "0.1.0",
"description": "{{folderName}}",
"private": true,
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"server": "DEBUG=vulcanize:* ts-node src/server.ts"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vulcanize/watcher-ts.git"
},
"author": "",
"license": "AGPL-3.0",
"bugs": {
"url": "https://github.com/vulcanize/watcher-ts/issues"
},
"homepage": "https://github.com/vulcanize/watcher-ts#readme",
"dependencies": {
"@apollo/client": "^3.3.19",
"@vulcanize/cache": "^0.1.0",
"@vulcanize/ipld-eth-client": "^0.1.0",
"@vulcanize/solidity-mapper": "^0.1.0",
"@vulcanize/util": "^0.1.0",
"apollo-server-express": "^2.25.0",
"apollo-type-bigint": "^0.1.3",
"debug": "^4.3.1",
"ethers": "^5.2.0",
"express": "^4.17.1",
"graphql": "^15.5.0",
"graphql-import-node": "^0.0.4",
"reflect-metadata": "^0.1.13",
"yargs": "^17.0.1"
},
"devDependencies": {
"@ethersproject/abi": "^5.3.0",
"@types/express": "^4.17.11",
"@types/yargs": "^17.0.0",
"ts-node": "^10.0.0",
"typescript": "^4.3.2"
}
}

View File

@ -0,0 +1,51 @@
# {{contractName}} Watcher
## Setup
* Run the following command to install required packages:
```bash
yarn
```
* Create a postgres12 database for the watcher:
```bash
sudo su - postgres
createdb {{folderName}}
```
* Update `environments/local.toml` with database connection settings.
* Update the `upstream` config in `environments/local.toml` and provide the `ipld-eth-server` GQL API and the `indexer-db` postgraphile endpoints.
## Run
* Run the watcher:
```bash
yarn server
```
GQL console: http://localhost:3008/graphql
## Demo
* Install required packages:
```bash
yarn
```
* Create the database:
```bash
sudo su - postgres
createdb {{folderName}}
```
* Run the watcher:
```bash
yarn server
```

View File

@ -0,0 +1,46 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import BigInt from 'apollo-type-bigint';
import debug from 'debug';
import { ValueResult } from '@vulcanize/util';
import { Indexer } from './indexer';
const log = debug('vulcanize:resolver');
export const createResolvers = async (indexer: Indexer): Promise<any> => {
assert(indexer);
return {
BigInt: new BigInt('bigInt'),
Event: {
__resolveType: (obj: any) => {
assert(obj.__typename);
return obj.__typename;
}
},
Query: {
{{#each queries}}
{{this.name}}: (_: any, { blockHash, contractAddress
{{~#each this.params}}, {{this.name~}} {{/each}} }: { blockHash: string, contractAddress: string
{{~#each this.params}}, {{this.name}}: {{this.type~}} {{/each}} }): Promise<ValueResult> => {
log('{{this.name}}', blockHash, contractAddress
{{~#each this.params}}, {{this.name~}} {{/each}});
return indexer.{{this.name}}(blockHash, contractAddress
{{~#each this.params}}, {{this.name~}} {{/each}});
}
{{~#unless @last}},
{{/unless}}
{{/each}}
}
};
};

View File

@ -0,0 +1,97 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import fs from 'fs';
import path from 'path';
import assert from 'assert';
import 'reflect-metadata';
import express, { Application } from 'express';
import { ApolloServer, PubSub } from 'apollo-server-express';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import debug from 'debug';
import 'graphql-import-node';
import { createServer } from 'http';
import { getDefaultProvider } from 'ethers';
import { getCache } from '@vulcanize/cache';
import { EthClient } from '@vulcanize/ipld-eth-client';
import { DEFAULT_CONFIG_PATH, getConfig } from '@vulcanize/util';
import { createResolvers } from './resolvers';
import { Indexer } from './indexer';
import { Database } from './database';
const log = debug('vulcanize:server');
export const main = async (): Promise<any> => {
const argv = await yargs(hideBin(process.argv))
.option('f', {
alias: 'config-file',
demandOption: true,
describe: 'configuration file path (toml)',
type: 'string',
default: DEFAULT_CONFIG_PATH
})
.argv;
const config = await getConfig(argv.f);
assert(config.server, 'Missing server config');
const { host, port, mode } = config.server;
const { upstream, database: dbConfig } = config;
assert(dbConfig, 'Missing database config');
const db = new Database(dbConfig);
await db.init();
assert(upstream, 'Missing upstream config');
const { ethServer: { gqlApiEndpoint, gqlPostgraphileEndpoint, rpcProviderEndpoint }, cache: cacheConfig } = upstream;
assert(gqlApiEndpoint, 'Missing upstream ethServer.gqlApiEndpoint');
assert(gqlPostgraphileEndpoint, 'Missing upstream ethServer.gqlPostgraphileEndpoint');
const cache = await getCache(cacheConfig);
const ethClient = new EthClient({
gqlEndpoint: gqlApiEndpoint,
gqlSubscriptionEndpoint: gqlPostgraphileEndpoint,
cache
});
const ethProvider = getDefaultProvider(rpcProviderEndpoint);
// Note: In-memory pubsub works fine for now, as each watcher is a single process anyway.
// Later: https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries
const pubsub = new PubSub();
const indexer = new Indexer(db, ethClient, ethProvider, mode);
const resolvers = await createResolvers(indexer);
const app: Application = express();
const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.gql')).toString();
const server = new ApolloServer({
typeDefs,
resolvers
});
await server.start();
server.applyMiddleware({ app });
const httpServer = createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen(port, host, () => {
log(`Server is listening on host ${host} port ${port}`);
});
return { app, server };
};
main().then(() => {
log('Starting server...');
}).catch(err => {
log(err);
});

View File

@ -0,0 +1,74 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
"resolveJsonModule": true /* Enabling the option allows importing JSON, and validating the types in that JSON file. */
},
"include": ["src/**/*"]
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import fs from 'fs';
import path from 'path';
import Handlebars from 'handlebars';
import { Writable } from 'stream';
const TEMPLATE_FILE = './templates/tsconfig-template.handlebars';
/**
* Writes the tsconfig.json file generated from a template to a stream.
* @param outStream A writable output stream to write the tsconfig.json file to.
*/
export function exportTSConfig (outStream: Writable): void {
const templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
const template = Handlebars.compile(templateString);
const tsconfig = template({});
outStream.write(tsconfig);
}

View File

@ -3,3 +3,4 @@
//
declare module '@poanet/solidity-flattener';
declare module 'solc';

View File

@ -1,6 +1,6 @@
{
"name": "common",
"version": "0.1.0",
"license": "AGPL-3.0",
"typings": "main.d.ts"
"name": "common",
"version": "0.1.0",
"license": "AGPL-3.0",
"typings": "main.d.ts"
}

View File

@ -0,0 +1,46 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
/**
* Helper function to compare two values using the given operator.
* @param lvalue Left hand side value.
* @param rvalue Right hasd side value.
* @param options Handlebars options parameter. `options.hash.operator`: operator to be used for comparison.
* @returns Result of the comparison.
*/
export function compareHelper (lvalue: string, rvalue: string, options: any): boolean {
assert(lvalue && rvalue, "Handlerbars Helper 'compare' needs at least 2 parameters");
const operator = options.hash.operator || '===';
const operators: Map<string, (l:any, r:any) => boolean> = new Map();
operators.set('===', function (l: any, r: any) { return l === r; });
operators.set('!==', function (l: any, r: any) { return l !== r; });
operators.set('<', function (l: any, r: any) { return l < r; });
operators.set('>', function (l: any, r: any) { return l > r; });
operators.set('<=', function (l: any, r: any) { return l <= r; });
operators.set('>=', function (l: any, r: any) { return l >= r; });
const operatorFunction = operators.get(operator);
assert(operatorFunction, "Handlerbars Helper 'compare' doesn't know the operator " + operator);
const result = operatorFunction(lvalue, rvalue);
return result;
}
/**
* Helper function that capitalized string till given index.
* @param value String of which content is to be capitalized.
* @param options Handlebars options parameter. `options.hash.tillIndex`: index till which to capitalize the string.
* @returns The modified string.
*/
export function capitalizeHelper (value: string, options: any): string {
const tillIndex = options.hash.tillIndex || value.length;
const result = `${value.slice(0, tillIndex).toUpperCase()}${value.slice(tillIndex, value.length)}`;
return result;
}

View File

@ -0,0 +1,42 @@
//
// Copyright 2021 Vulcanize, Inc.
//
const _solToTs: Map<string, string> = new Map();
const _tsToGql: Map<string, string> = new Map();
const _tsToPg: Map<string, string> = new Map();
// TODO Get typemapping from ethersjs.
// Solidity to Typescript type-mapping.
_solToTs.set('string', 'string');
_solToTs.set('uint8', 'number');
_solToTs.set('uint256', 'bigint');
_solToTs.set('address', 'string');
_solToTs.set('bool', 'boolean');
_solToTs.set('bytes4', 'string');
// Typescript to Graphql type-mapping.
_tsToGql.set('string', 'String');
_tsToGql.set('number', 'Int');
_tsToGql.set('bigint', 'BigInt');
_tsToGql.set('boolean', 'Boolean');
// Typescript to Postgres type-mapping.
_tsToPg.set('string', 'varchar');
_tsToPg.set('number', 'numeric');
_tsToPg.set('bigint', 'numeric');
_tsToPg.set('boolean', 'boolean');
function getTsForSol (solType: string): string | undefined {
return _solToTs.get(solType);
}
function getGqlForTs (tsType: string): string | undefined {
return _tsToGql.get(tsType);
}
function getPgForTs (tsType: string): string | undefined {
return _tsToPg.get(tsType);
}
export { getTsForSol, getGqlForTs, getPgForTs };

View File

@ -0,0 +1,8 @@
//
// Copyright 2021 Vulcanize, Inc.
//
export interface Param {
name: string;
type: string;
}

View File

@ -3,13 +3,27 @@
//
import { Writable } from 'stream';
import { Schema, Param } from './schema';
import { Database } from './database';
import { Entity } from './entity';
import { Indexer } from './indexer';
import { Resolvers } from './resolvers';
import { Schema } from './schema';
import { Param } from './utils/types';
export class Visitor {
_schema: Schema;
_resolvers: Resolvers;
_indexer: Indexer;
_entity: Entity;
_database: Database;
constructor () {
this._schema = new Schema();
this._resolvers = new Resolvers();
this._indexer = new Indexer();
this._entity = new Entity();
this._database = new Database();
}
/**
@ -27,6 +41,10 @@ export class Visitor {
const returnType = node.returnParameters[0].typeName.name;
this._schema.addQuery(name, params, returnType);
this._resolvers.addQuery(name, params, returnType);
this._indexer.addQuery(name, params, returnType);
this._entity.addQuery(name, params, returnType);
this._database.addQuery(name, params, returnType);
}
}
@ -56,6 +74,10 @@ export class Visitor {
const returnType = typeName.name;
this._schema.addQuery(name, params, returnType);
this._resolvers.addQuery(name, params, returnType);
this._indexer.addQuery(name, params, returnType);
this._entity.addQuery(name, params, returnType);
this._database.addQuery(name, params, returnType);
}
/**
@ -78,4 +100,37 @@ export class Visitor {
exportSchema (outStream: Writable): void {
this._schema.exportSchema(outStream);
}
/**
* Writes the resolvers file generated from a template to a stream.
* @param outStream A writable output stream to write the resolvers file to.
*/
exportResolvers (outStream: Writable): void {
this._resolvers.exportResolvers(outStream);
}
/**
* Writes the indexer file generated from a template to a stream.
* @param outStream A writable output stream to write the indexer file to.
* @param inputFileName Input contract file name to be passed to the template.
*/
exportIndexer (outStream: Writable, inputFileName: string): void {
this._indexer.exportIndexer(outStream, inputFileName);
}
/**
* Writes the generated entity files in the given directory.
* @param entityDir Directory to write the entities to.
*/
exportEntities (entityDir: string): void {
this._entity.exportEntities(entityDir);
}
/**
* Writes the database file generated from a template to a stream.
* @param outStream A writable output stream to write the database file to.
*/
exportDatabase (outStream: Writable): void {
this._database.exportDatabase(outStream);
}
}

View File

@ -44,12 +44,13 @@
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
"typeRoots": [
"./src/types"
"./src/types",
"node_modules/@types"
], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
@ -69,7 +70,8 @@
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
"resolveJsonModule": true /* Enabling the option allows importing JSON, and validating the types in that JSON file. */
},
"include": ["src/**/*"]
}

View File

@ -1991,7 +1991,7 @@
"@poanet/solidity-flattener@https://github.com/vulcanize/solidity-flattener.git":
version "3.0.6"
resolved "https://github.com/vulcanize/solidity-flattener.git#397f7295acff78eb039e479a85e8c3a9d78a96dd"
resolved "https://github.com/vulcanize/solidity-flattener.git#9c8a22b9c43515be306646a177a43636fd95aaae"
dependencies:
bunyan "^1.8.12"
decomment "^0.9.1"
@ -7578,7 +7578,7 @@ growl@1.10.5:
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
handlebars@^4.7.6:
handlebars@^4.7.6, handlebars@^4.7.7:
version "4.7.7"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
@ -12421,6 +12421,21 @@ solc@^0.6.3:
semver "^5.5.0"
tmp "0.0.33"
solc@^0.8.7-fixed:
version "0.8.7-fixed"
resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.7-fixed.tgz#76eb37d33637ad278ad858e2633e9da597d877ac"
integrity sha512-nWZRkdPwfBpimAelO30Bz7/hxoj+mylb30gEpBL8hhEWR4xqu2ezQAxWK1Hz5xx1NqesbgGjSgnGul49tRHWgQ==
dependencies:
command-exists "^1.2.8"
commander "3.0.2"
follow-redirects "^1.12.1"
fs-extra "^0.30.0"
js-sha3 "0.8.0"
memorystream "^0.3.1"
require-from-string "^2.0.0"
semver "^5.5.0"
tmp "0.0.33"
sort-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"