Add flag in codegen to ignore errors for unhandled types (#350)

* Add --continue-on-error flag to codegen for skipping unhandled data types

* Use solc version defined in config

* Fix codegen log for unhandled array types

* Log solc compilation errors

---------

Co-authored-by: Dhruv Srivastava <dhruvdhs.ds@gmail.com>
This commit is contained in:
Nabarun Gogoi 2023-04-06 15:24:11 +05:30 committed by GitHub
parent 8c928516f8
commit 754db311f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 81 additions and 21 deletions

View File

@ -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<Solc> {
return new Promise((resolve, reject) => {
solc.loadRemoteVersion(solcVersion, (err: any, solcInstance: Solc | Promise<any>) => {
if (err) {
reject(err);
} else {
resolve(solcInstance);
}
});
});
}

View File

@ -47,6 +47,13 @@ const main = async (): Promise<void> => {
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<void> => {
// 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<void> => {
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

View File

@ -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);
}
}
}