diff --git a/packages/codegen/src/artifacts.ts b/packages/codegen/src/artifacts.ts index 178e84ff..dda36e43 100644 --- a/packages/codegen/src/artifacts.ts +++ b/packages/codegen/src/artifacts.ts @@ -4,13 +4,17 @@ import solc from 'solc'; +interface Solc { + compile(input: string): any; +} + /** * Compiles the given contract using solc and returns resultant artifacts. * @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 generateArtifacts (contractContent: string, contractFileName: string, contractName: string): { abi: any[], storageLayout: any } { +export async function generateArtifacts (contractContent: string, contractFileName: string, contractName: string, solcVersion: string): Promise<{ abi: any[], storageLayout: any }> { const input: any = { language: 'Solidity', sources: {}, @@ -27,6 +31,31 @@ export function generateArtifacts (contractContent: string, contractFileName: st content: contractContent }; + const solcInstance = (solcVersion === undefined) ? solc : await getSolcByVersion(solcVersion); + const compiledContract = JSON.parse(solcInstance.compile(JSON.stringify(input))); + + if (compiledContract.errors?.length) { + compiledContract.errors.forEach((error: any) => { + if (error.severity === 'error') { + throw new Error(error.formattedMessage); + } + + console.log(`${error.severity}: ${error.formattedMessage}`); + }); + } + // Get artifacts for the required contract. - return JSON.parse(solc.compile(JSON.stringify(input))).contracts[contractFileName][contractName]; + return compiledContract.contracts[contractFileName][contractName]; +} + +async function getSolcByVersion (solcVersion: string): Promise { + return new Promise((resolve, reject) => { + solc.loadRemoteVersion(solcVersion, (err: any, solcInstance: Solc | Promise) => { + if (err) { + reject(err); + } else { + resolve(solcInstance); + } + }); + }); } diff --git a/packages/codegen/src/generate-code.ts b/packages/codegen/src/generate-code.ts index 7866d1d7..74ac5f4f 100644 --- a/packages/codegen/src/generate-code.ts +++ b/packages/codegen/src/generate-code.ts @@ -47,6 +47,13 @@ const main = async (): Promise => { describe: 'Watcher generation config file path (yaml)', type: 'string' }) + .option('continue-on-error', { + alias: 'e', + demandOption: false, + default: false, + describe: 'Continue generating watcher if unhandled types encountered', + type: 'boolean' + }) .argv; const config = getConfig(path.resolve(argv['config-file'])); @@ -83,10 +90,11 @@ const main = async (): Promise => { // Generate artifacts from contract. const inputFileName = path.basename(inputFile, '.sol'); - const { abi, storageLayout } = generateArtifacts( + const { abi, storageLayout } = await generateArtifacts( contractData.contractString, `${inputFileName}.sol`, - contractData.contractName + contractData.contractName, + config.solc ); contractData.contractAbi = abi; @@ -96,7 +104,8 @@ const main = async (): Promise => { contracts.push(contractData); } - const visitor = new Visitor(); + const continueOnError = argv['continue-on-error']; + const visitor = new Visitor(continueOnError); parseAndVisit(visitor, contracts, config.mode); @@ -379,6 +388,7 @@ function getConfig (configFile: string): any { mode: inputConfig.mode || MODE_ALL, kind: inputConfig.kind || KIND_ACTIVE, port: inputConfig.port || DEFAULT_PORT, + solc: inputConfig.solc, flatten, subgraphPath, subgraphConfig diff --git a/packages/codegen/src/visitor.ts b/packages/codegen/src/visitor.ts index 07d2a783..ec9e3b0b 100644 --- a/packages/codegen/src/visitor.ts +++ b/packages/codegen/src/visitor.ts @@ -25,10 +25,11 @@ export class Visitor { _database: Database; _client: Client; _types: Types; + _continueOnError: boolean; _contract?: { name: string, kind: string }; - constructor () { + constructor (continueOnErrorFlag = false) { this._schema = new Schema(); this._resolvers = new Resolvers(); this._indexer = new Indexer(); @@ -36,6 +37,7 @@ export class Visitor { this._database = new Database(); this._client = new Client(); this._types = new Types(); + this._continueOnError = continueOnErrorFlag; } setContract (name: string, kind: string): void { @@ -56,25 +58,44 @@ export class Visitor { return { name: item.name, type: item.typeName.name }; }); - const typeName = node.returnParameters[0].typeName; + let errorMessage = ''; - // TODO Handle user defined type return. - if (typeName.type === 'UserDefinedTypeName') { - // Skip in case of UserDefinedTypeName. - return; + const typeName = node.returnParameters[0].typeName; + switch (typeName.type) { + case 'ElementaryTypeName': { + const returnType = typeName.name; + + this._schema.addQuery(name, params, returnType); + this._resolvers.addQuery(name, params, returnType); + this._entity.addQuery(name, params, returnType); + this._database.addQuery(name, params, returnType); + this._client.addQuery(name, params, returnType); + + assert(this._contract); + this._indexer.addQuery(this._contract.name, MODE_ETH_CALL, name, params, returnType); + + break; + } + case 'UserDefinedTypeName': + errorMessage = `No support in codegen for user defined return type from method "${node.name}"`; + break; + + case 'ArrayTypeName': + errorMessage = `No support in codegen for return type "${typeName.baseTypeName.name}[]" from method "${node.name}"`; + break; + + default: + errorMessage = `No support in codegen for return type "${typeName.type}" from method "${node.name}"`; } - // TODO Handle multiple return parameters and array return type. - const returnType = typeName.name; + if (errorMessage !== '') { + if (this._continueOnError) { + console.log(errorMessage); + return; + } - this._schema.addQuery(name, params, returnType); - this._resolvers.addQuery(name, params, returnType); - this._entity.addQuery(name, params, returnType); - this._database.addQuery(name, params, returnType); - this._client.addQuery(name, params, returnType); - - assert(this._contract); - this._indexer.addQuery(this._contract.name, MODE_ETH_CALL, name, params, returnType); + throw new Error(errorMessage); + } } }