mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-04-13 19:01:16 +00:00
Support artifacts generation for multiple contracts (#82)
This commit is contained in:
parent
3638d56787
commit
5b12db541b
@ -21,11 +21,11 @@
|
||||
* Run the following command to generate a watcher from a contract file:
|
||||
|
||||
```bash
|
||||
yarn codegen --input-file <input-file-path> --contract-name <contract-name> --output-folder [output-folder] --mode [eth_call | storage | all] --flatten [true | false] --kind [lazy | active] --port [server-port] --subgraph-path [subgraph-build-path]
|
||||
yarn codegen --input-files <input-file-paths> --contract-names <contract-names> --output-folder [output-folder] --mode [eth_call | storage | all] --flatten [true | false] --kind [lazy | active] --port [server-port] --subgraph-path [subgraph-build-path]
|
||||
```
|
||||
|
||||
* `input-file`(alias: `i`): Input contract file path or an URL (required).
|
||||
* `contract-name`(alias: `c`): Main contract name (required).
|
||||
* `input-files`(alias: `i`): Input contract file path(s) or URL(s) (required).
|
||||
* `contract-names`(alias: `c`): Contract name(s) (in order of `input-files`) (required).
|
||||
* `output-folder`(alias: `o`): Output folder path. (logs output using `stdout` if not provided).
|
||||
* `mode`(alias: `m`): Code generation mode (default: `all`).
|
||||
* `flatten`(alias: `f`): Flatten the input contract file (default: `true`).
|
||||
@ -33,36 +33,42 @@
|
||||
* `port` (alias: `p`): Server port (default: `3008`).
|
||||
* `subgraph-path` (alias: `s`): Path to the subgraph build.
|
||||
|
||||
**Note**: When passed an *URL* as `input-file`, it is assumed that it points to an already flattened contract file.
|
||||
**Note**: When passed an *URL* in `input-files`, it is assumed that it points to an already flattened contract file.
|
||||
|
||||
Examples:
|
||||
|
||||
Generate code in `storage` mode, `lazy` kind.
|
||||
|
||||
```bash
|
||||
yarn codegen --input-file ./test/examples/contracts/ERC721.sol --contract-name ERC721 --output-folder ../my-erc721-watcher --mode storage --kind lazy
|
||||
yarn codegen --input-files ./test/examples/contracts/ERC721.sol --contract-names ERC721 --output-folder ../my-erc721-watcher --mode storage --kind lazy
|
||||
```
|
||||
|
||||
Generate code in `eth_call` mode using a contract provided by an URL.
|
||||
|
||||
```bash
|
||||
yarn codegen --input-file https://git.io/Jupci --contract-name ERC721 --output-folder ../my-erc721-watcher --mode eth_call
|
||||
yarn codegen --input-files https://git.io/Jupci --contract-names ERC721 --output-folder ../my-erc721-watcher --mode eth_call
|
||||
```
|
||||
|
||||
Generate code for `ERC721` in both `eth_call` and `storage` mode, `active` kind.
|
||||
|
||||
```bash
|
||||
yarn codegen --input-file ../../node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol --contract-name ERC721 --output-folder ../demo-erc721-watcher --mode all --kind active
|
||||
yarn codegen --input-files ../../node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol --contract-names ERC721 --output-folder ../demo-erc721-watcher --mode all --kind active
|
||||
```
|
||||
|
||||
Generate code for `ERC20` contract in both `eth_call` and `storage` mode, `active` kind:
|
||||
|
||||
```bash
|
||||
yarn codegen --input-file ../../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol --contract-name ERC20 --output-folder ../demo-erc20-watcher --mode all --kind active
|
||||
yarn codegen --input-files ../../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol --contract-names ERC20 --output-folder ../demo-erc20-watcher --mode all --kind active
|
||||
```
|
||||
|
||||
This will create a folder called `demo-erc20-watcher` containing the generated code at the specified path. Follow the steps in [Run Generated Watcher](#run-generated-watcher) to setup and run the generated watcher.
|
||||
|
||||
Generate code for `Eden` contracts in `storage` mode, `active` kind:
|
||||
|
||||
```bash
|
||||
yarn codegen --input-files ~/vulcanize/governance/contracts/EdenNetwork.sol ~/vulcanize/governance/contracts/MerkleDistributor.sol ~/vulcanize/governance/contracts/DistributorGovernance.sol --contract-names EdenNetwork MerkleDistributor DistributorGovernance --output-folder ../demo-eden-watcher --mode storage --kind active --subgraph-path ~/vulcanize/eden-data/packages/subgraph/build
|
||||
```
|
||||
|
||||
## Run Generated Watcher
|
||||
|
||||
### Setup
|
||||
|
@ -7,6 +7,8 @@ import fetch from 'node-fetch';
|
||||
import path from 'path';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import assert from 'assert';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
import { flatten } from '@poanet/solidity-flattener';
|
||||
import { parse, visit } from '@solidity-parser/parser';
|
||||
@ -35,17 +37,17 @@ import { exportInspectCID } from './inspect-cid';
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
const argv = await yargs(hideBin(process.argv))
|
||||
.option('input-file', {
|
||||
.option('input-files', {
|
||||
alias: 'i',
|
||||
demandOption: true,
|
||||
describe: 'Input contract file path or an url.',
|
||||
type: 'string'
|
||||
describe: 'Input contract file path(s) or url(s).',
|
||||
type: 'array'
|
||||
})
|
||||
.option('contract-name', {
|
||||
.option('contract-names', {
|
||||
alias: 'c',
|
||||
demandOption: true,
|
||||
describe: 'Main contract name.',
|
||||
type: 'string'
|
||||
describe: 'Contract name(s).',
|
||||
type: 'array'
|
||||
})
|
||||
.option('output-folder', {
|
||||
alias: 'o',
|
||||
@ -85,47 +87,57 @@ const main = async (): Promise<void> => {
|
||||
})
|
||||
.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();
|
||||
// Create an array of flattened contract strings.
|
||||
const contractStrings: string[] = [];
|
||||
|
||||
for (const inputFile of argv['input-files']) {
|
||||
assert(typeof inputFile === 'string', 'Input file path should be a string');
|
||||
|
||||
if (inputFile.startsWith('http')) {
|
||||
// Assume flattened file in case of URL.
|
||||
const response = await fetch(inputFile);
|
||||
const contractString = await response.text();
|
||||
contractStrings.push(contractString);
|
||||
} else {
|
||||
contractStrings.push(argv.flatten
|
||||
? await flatten(path.resolve(inputFile))
|
||||
: fs.readFileSync(path.resolve(inputFile)).toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const visitor = new Visitor();
|
||||
|
||||
parseAndVisit(data, visitor, argv.mode);
|
||||
parseAndVisit(contractStrings, visitor, argv.mode);
|
||||
|
||||
generateWatcher(data, visitor, argv);
|
||||
generateWatcher(contractStrings, visitor, argv);
|
||||
};
|
||||
|
||||
function parseAndVisit (data: string, visitor: Visitor, mode: string) {
|
||||
// Get the abstract syntax tree for the flattened contract.
|
||||
const ast = parse(data);
|
||||
function parseAndVisit (contractStrings: string[], visitor: Visitor, mode: string) {
|
||||
for (const contractString of contractStrings) {
|
||||
// Get the abstract syntax tree for the flattened contract.
|
||||
const ast = parse(contractString);
|
||||
|
||||
// Filter out library nodes.
|
||||
ast.children = ast.children.filter(child => !(child.type === 'ContractDefinition' && child.kind === 'library'));
|
||||
// Filter out library nodes.
|
||||
ast.children = ast.children.filter(child => !(child.type === 'ContractDefinition' && child.kind === 'library'));
|
||||
|
||||
if ([MODE_ALL, MODE_ETH_CALL].some(value => value === mode)) {
|
||||
visit(ast, {
|
||||
FunctionDefinition: visitor.functionDefinitionVisitor.bind(visitor),
|
||||
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
|
||||
});
|
||||
}
|
||||
if ([MODE_ALL, MODE_ETH_CALL].includes(mode)) {
|
||||
visit(ast, {
|
||||
FunctionDefinition: visitor.functionDefinitionVisitor.bind(visitor),
|
||||
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
|
||||
});
|
||||
}
|
||||
|
||||
if ([MODE_ALL, MODE_STORAGE].some(value => value === mode)) {
|
||||
visit(ast, {
|
||||
StateVariableDeclaration: visitor.stateVariableDeclarationVisitor.bind(visitor),
|
||||
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
|
||||
});
|
||||
if ([MODE_ALL, MODE_STORAGE].includes(mode)) {
|
||||
visit(ast, {
|
||||
StateVariableDeclaration: visitor.stateVariableDeclarationVisitor.bind(visitor),
|
||||
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
function generateWatcher (contractStrings: string[], visitor: Visitor, argv: any) {
|
||||
// Prepare directory structure for the watcher.
|
||||
let outputDir = '';
|
||||
if (argv['output-folder']) {
|
||||
@ -145,13 +157,34 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
if (!fs.existsSync(resetCmdsFolder)) fs.mkdirSync(resetCmdsFolder, { recursive: true });
|
||||
}
|
||||
|
||||
const inputFileName = path.basename(argv['input-file'], '.sol');
|
||||
let outStream: Writable;
|
||||
|
||||
const contractNames = argv['contract-names'];
|
||||
const inputFileNames: string[] = [];
|
||||
|
||||
// Export artifacts for the contracts.
|
||||
argv['input-files'].forEach((inputFile: string, index: number) => {
|
||||
const inputFileName = path.basename(inputFile, '.sol');
|
||||
inputFileNames.push(inputFileName);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/artifacts/', `${inputFileName}.json`))
|
||||
: process.stdout;
|
||||
|
||||
exportArtifacts(
|
||||
outStream,
|
||||
contractStrings[index],
|
||||
`${inputFileName}.sol`,
|
||||
contractNames[index]
|
||||
);
|
||||
});
|
||||
|
||||
// Register the handlebar helpers to be used in the templates.
|
||||
registerHandlebarHelpers();
|
||||
|
||||
visitor.visitSubgraph(argv['subgraph-path']);
|
||||
|
||||
let outStream = outputDir
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/schema.gql'))
|
||||
: process.stdout;
|
||||
const schemaContent = visitor.exportSchema(outStream);
|
||||
@ -164,7 +197,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/indexer.ts'))
|
||||
: process.stdout;
|
||||
visitor.exportIndexer(outStream, inputFileName, argv['contract-name']);
|
||||
visitor.exportIndexer(outStream, inputFileNames);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/server.ts'))
|
||||
@ -176,16 +209,6 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
: process.stdout;
|
||||
exportConfig(argv.kind, argv.port, path.basename(outputDir), outStream, argv['subgraph-path']);
|
||||
|
||||
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;
|
||||
@ -242,6 +265,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
exportFill(outStream);
|
||||
|
||||
let rcOutStream, ignoreOutStream;
|
||||
|
||||
if (outputDir) {
|
||||
rcOutStream = fs.createWriteStream(path.join(outputDir, '.eslintrc.json'));
|
||||
ignoreOutStream = fs.createWriteStream(path.join(outputDir, '.eslintignore'));
|
||||
@ -249,6 +273,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
rcOutStream = process.stdout;
|
||||
ignoreOutStream = process.stdout;
|
||||
}
|
||||
|
||||
exportLint(rcOutStream, ignoreOutStream);
|
||||
|
||||
outStream = outputDir
|
||||
@ -257,6 +282,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
visitor.exportClient(outStream, schemaContent, path.join(outputDir, 'src/gql'));
|
||||
|
||||
let resetOutStream, resetJQOutStream, resetStateOutStream;
|
||||
|
||||
if (outputDir) {
|
||||
resetOutStream = fs.createWriteStream(path.join(outputDir, 'src/cli/reset.ts'));
|
||||
resetJQOutStream = fs.createWriteStream(path.join(outputDir, 'src/cli/reset-cmds/job-queue.ts'));
|
||||
@ -266,6 +292,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
resetJQOutStream = process.stdout;
|
||||
resetStateOutStream = process.stdout;
|
||||
}
|
||||
|
||||
visitor.exportReset(resetOutStream, resetJQOutStream, resetStateOutStream);
|
||||
|
||||
outStream = outputDir
|
||||
|
@ -104,12 +104,11 @@ export class Indexer {
|
||||
* @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, contractName: string): void {
|
||||
exportIndexer (outStream: Writable, inputFileNames: string[]): void {
|
||||
const template = Handlebars.compile(this._templateString);
|
||||
|
||||
const obj = {
|
||||
inputFileName,
|
||||
contractName,
|
||||
inputFileNames,
|
||||
queries: this._queries,
|
||||
constants: {
|
||||
MODE_ETH_CALL,
|
||||
|
@ -36,7 +36,7 @@ export class Schema {
|
||||
|
||||
// TODO: Handle cases where returnType/params type is an array.
|
||||
const tsReturnType = getTsForSol(returnType);
|
||||
assert(tsReturnType);
|
||||
assert(tsReturnType, `ts type for sol type ${returnType} for ${name} not found`);
|
||||
|
||||
const queryObject: { [key: string]: any; } = {};
|
||||
queryObject[name] = {
|
||||
@ -51,7 +51,7 @@ export class Schema {
|
||||
if (params.length > 0) {
|
||||
queryObject[name].args = params.reduce((acc, curr) => {
|
||||
const tsCurrType = getTsForSol(curr.type);
|
||||
assert(tsCurrType);
|
||||
assert(tsCurrType, `ts type for sol type ${curr.type} for ${curr.name} not found`);
|
||||
acc[curr.name] = `${getGqlForTs(tsCurrType)}!`;
|
||||
return acc;
|
||||
}, queryObject[name].args);
|
||||
@ -81,7 +81,7 @@ export class Schema {
|
||||
if (params.length > 0) {
|
||||
typeObject.fields = params.reduce((acc, curr) => {
|
||||
const tsCurrType = getTsForSol(curr.type);
|
||||
assert(tsCurrType);
|
||||
assert(tsCurrType, `ts type for sol type ${curr.type} for ${curr.name} not found`);
|
||||
acc[curr.name] = `${getGqlForTs(tsCurrType)}!`;
|
||||
return acc;
|
||||
}, typeObject.fields);
|
||||
|
@ -26,7 +26,11 @@ import { SyncStatus } from './entity/SyncStatus';
|
||||
import { HookStatus } from './entity/HookStatus';
|
||||
import { BlockProgress } from './entity/BlockProgress';
|
||||
import { IPLDBlock } from './entity/IPLDBlock';
|
||||
|
||||
{{#each inputFileNames as | inputFileName |}}
|
||||
import artifacts from './artifacts/{{inputFileName}}.json';
|
||||
{{/each}}
|
||||
|
||||
import { createInitialCheckpoint, handleEvent, createStateDiff, createStateCheckpoint } from './hooks';
|
||||
import { IPFSClient } from './ipfs';
|
||||
|
||||
|
@ -11,10 +11,15 @@ const _gqlToTs: Map<string, string> = new Map();
|
||||
// Solidity to Typescript type-mapping.
|
||||
_solToTs.set('string', 'string');
|
||||
_solToTs.set('uint8', 'number');
|
||||
_solToTs.set('uint16', 'number');
|
||||
_solToTs.set('uint64', 'bigint');
|
||||
_solToTs.set('uint128', 'bigint');
|
||||
_solToTs.set('uint256', 'bigint');
|
||||
_solToTs.set('address', 'string');
|
||||
_solToTs.set('bool', 'boolean');
|
||||
_solToTs.set('bytes', 'string');
|
||||
_solToTs.set('bytes4', 'string');
|
||||
_solToTs.set('bytes32', 'string');
|
||||
|
||||
// Typescript to Graphql type-mapping.
|
||||
_tsToGql.set('string', 'String');
|
||||
|
@ -140,10 +140,10 @@ export class Visitor {
|
||||
/**
|
||||
* 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.
|
||||
* @param inputFileName Input contract file names to be passed to the template.
|
||||
*/
|
||||
exportIndexer (outStream: Writable, inputFileName: string, contractName: string): void {
|
||||
this._indexer.exportIndexer(outStream, inputFileName, contractName);
|
||||
exportIndexer (outStream: Writable, inputFileNames: string[]): void {
|
||||
this._indexer.exportIndexer(outStream, inputFileNames);
|
||||
}
|
||||
|
||||
/**
|
||||
|
14
yarn.lock
14
yarn.lock
@ -2032,7 +2032,7 @@
|
||||
|
||||
"@poanet/solidity-flattener@https://github.com/vulcanize/solidity-flattener.git":
|
||||
version "3.0.6"
|
||||
resolved "https://github.com/vulcanize/solidity-flattener.git#9c8a22b9c43515be306646a177a43636fd95aaae"
|
||||
resolved "https://github.com/vulcanize/solidity-flattener.git#144ef6cda8823f4a5e48cb4f615be87a32e2dcbc"
|
||||
dependencies:
|
||||
bunyan "^1.8.12"
|
||||
decomment "^0.9.1"
|
||||
@ -2431,9 +2431,9 @@
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/glob@*":
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672"
|
||||
integrity sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
|
||||
integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==
|
||||
dependencies:
|
||||
"@types/minimatch" "*"
|
||||
"@types/node" "*"
|
||||
@ -5508,9 +5508,9 @@ decode-uri-component@^0.2.0:
|
||||
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
||||
|
||||
decomment@^0.9.1:
|
||||
version "0.9.4"
|
||||
resolved "https://registry.yarnpkg.com/decomment/-/decomment-0.9.4.tgz#fa40335bd90e3826d5c1984276e390525ff856d5"
|
||||
integrity sha512-8eNlhyI5cSU4UbBlrtagWpR03dqXcE5IR9zpe7PnO6UzReXDskucsD8usgrzUmQ6qJ3N82aws/p/mu/jqbURWw==
|
||||
version "0.9.5"
|
||||
resolved "https://registry.yarnpkg.com/decomment/-/decomment-0.9.5.tgz#61753c80b8949620eb6bc3f8246cc0e2720ceac1"
|
||||
integrity sha512-h0TZ8t6Dp49duwyDHo3iw67mnh9/UpFiSSiOb5gDK1sqoXzrfX/SQxIUQd2R2QEiSnqib0KF2fnKnGfAhAs6lg==
|
||||
dependencies:
|
||||
esprima "4.0.1"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user