diff --git a/packages/codegen/src/indexer.ts b/packages/codegen/src/indexer.ts
index adc48e58..4ec46dc5 100644
--- a/packages/codegen/src/indexer.ts
+++ b/packages/codegen/src/indexer.ts
@@ -9,6 +9,8 @@ import Handlebars from 'handlebars';
import { Writable } from 'stream';
import _ from 'lodash';
+import { VariableDeclaration } from '@solidity-parser/parser/dist/src/ast-types';
+
import { getGqlForSol, getTsForGql } from './utils/type-mappings';
import { Param } from './utils/types';
import { MODE_ETH_CALL, MODE_STORAGE } from './utils/constants';
@@ -42,23 +44,46 @@ export class Indexer {
* @param returnType Return type for the query.
* @param stateVariableType Type of the state variable in case of state variable query.
*/
- addQuery (contract: string, mode: string, name: string, params: Array, typeName: any, stateVariableType?: string): void {
+ addQuery (
+ contract: string,
+ mode: string,
+ name: string,
+ params: Array,
+ returnParameters: VariableDeclaration[],
+ stateVariableType?: string
+ ): void {
// Check if the query is already added.
if (this._queries.some(query => query.name === name)) {
return;
}
- const baseType = getBaseType(typeName);
- assert(baseType);
- const gqlReturnType = getGqlForSol(baseType);
- assert(gqlReturnType);
- let tsReturnType = getTsForGql(gqlReturnType);
- assert(tsReturnType);
+ // Disable DB caching if more than 1 return params.
+ let disableCaching = returnParameters.length > 1;
- const isArray = isArrayType(typeName);
- if (isArray) {
- tsReturnType = tsReturnType.concat('[]');
- }
+ const returnTypes = returnParameters.map(returnParameter => {
+ let typeName = returnParameter.typeName;
+ assert(typeName);
+
+ // Handle Mapping type for state variable queries
+ while (typeName.type === 'Mapping') {
+ typeName = typeName.valueType;
+ }
+
+ const baseType = getBaseType(typeName);
+ assert(baseType);
+ const gqlReturnType = getGqlForSol(baseType);
+ assert(gqlReturnType);
+ let tsReturnType = getTsForGql(gqlReturnType);
+ assert(tsReturnType);
+
+ const isArray = isArrayType(typeName);
+ if (isArray) {
+ disableCaching = true;
+ tsReturnType = tsReturnType.concat('[]');
+ }
+
+ return tsReturnType;
+ });
const queryObject = {
name,
@@ -66,11 +91,11 @@ export class Indexer {
getQueryName: '',
saveQueryName: '',
params: _.cloneDeep(params),
- returnType: tsReturnType,
+ returnTypes,
mode,
stateVariableType,
contract,
- disableCaching: isArray
+ disableCaching
};
if (name.charAt(0) === '_') {
diff --git a/packages/codegen/src/resolvers.ts b/packages/codegen/src/resolvers.ts
index 701dfedf..8e997721 100644
--- a/packages/codegen/src/resolvers.ts
+++ b/packages/codegen/src/resolvers.ts
@@ -11,7 +11,6 @@ import _ from 'lodash';
import { getGqlForSol, getTsForGql } from './utils/type-mappings';
import { Param } from './utils/types';
-import { getBaseType } from './utils/helpers';
const TEMPLATE_FILE = './templates/resolvers-template.handlebars';
@@ -30,20 +29,16 @@ export class Resolvers {
* 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, typeName: any): void {
+ addQuery (name: string, params: Array): void {
// Check if the query is already added.
if (this._queries.some(query => query.name === name)) {
return;
}
- const returnType = getBaseType(typeName);
- assert(returnType);
const queryObject = {
name,
- params: _.cloneDeep(params),
- returnType
+ params: _.cloneDeep(params)
};
queryObject.params = queryObject.params.map((param) => {
diff --git a/packages/codegen/src/schema.ts b/packages/codegen/src/schema.ts
index 401056db..09dac1e7 100644
--- a/packages/codegen/src/schema.ts
+++ b/packages/codegen/src/schema.ts
@@ -7,6 +7,7 @@ import { GraphQLSchema, parse, printSchema, print, GraphQLDirective, GraphQLInt,
import { ObjectTypeComposer, ObjectTypeComposerDefinition, ObjectTypeComposerFieldConfigMapDefinition, SchemaComposer } from 'graphql-compose';
import { Writable } from 'stream';
import { utils } from 'ethers';
+import { VariableDeclaration } from '@solidity-parser/parser/dist/src/ast-types';
import { getGqlForTs, getGqlForSol } from './utils/type-mappings';
import { Param } from './utils/types';
@@ -30,21 +31,13 @@ export class Schema {
* @param params Parameters to the query.
* @param returnType Return type for the query.
*/
- addQuery (name: string, params: Array, typeName: any): void {
+ addQuery (name: string, params: Array, returnParameters: VariableDeclaration[]): void {
// Check if the query is already added.
if (this._composer.Query.hasField(name)) {
return;
}
- // TODO: Handle cases where returnType/params type is an array.
- const isReturnTypeArray = isArrayType(typeName);
- const baseTypeName = getBaseType(typeName);
- assert(baseTypeName);
-
- const gqlReturnType = getGqlForSol(baseTypeName);
- assert(gqlReturnType, `gql type for sol type ${baseTypeName} for ${name} not found`);
-
- const objectTC = this._getOrCreateResultType(gqlReturnType, isReturnTypeArray);
+ const objectTC = this._getOrCreateResultType(name, returnParameters);
const queryObject: { [key: string]: any; } = {};
queryObject[name] = {
@@ -57,6 +50,7 @@ export class Schema {
};
if (params.length > 0) {
+ // TODO: Handle cases where params type is an array.
queryObject[name].args = params.reduce((acc, curr) => {
acc[curr.name] = `${getGqlForSol(curr.type)}!`;
return acc;
@@ -242,19 +236,65 @@ export class Schema {
/**
* Adds Result types to the schema and typemapping.
*/
- _getOrCreateResultType (typeName: string, isArray = false): ObjectTypeComposer {
- const value = `${typeName}!`;
+ _getOrCreateResultType (functionName: string, returnParameters: VariableDeclaration[]): ObjectTypeComposer {
+ const returnValueTypes = returnParameters.map((returnParameter) => {
+ let typeName = returnParameter.typeName;
+ assert(typeName);
- let objectTCName = `Result${typeName}`;
- if (isArray) {
- objectTCName = objectTCName.concat('Array');
+ // Handle Mapping type for state variable queries
+ while (typeName.type === 'Mapping') {
+ typeName = typeName.valueType;
+ }
+
+ const isReturnTypeArray = isArrayType(typeName);
+ const baseTypeName = getBaseType(typeName);
+ assert(baseTypeName);
+
+ const gqlReturnType = getGqlForSol(baseTypeName);
+ assert(gqlReturnType, `gql type for sol type ${baseTypeName} for ${functionName} not found`);
+
+ return {
+ type: gqlReturnType,
+ isArray: isReturnTypeArray
+ };
+ });
+
+ let objectTCName = 'Result';
+ let value = '';
+
+ if (returnParameters.length > 1) {
+ const returnValueTypesMap = returnParameters.reduce((acc: {[key: string]: string}, _, index) => {
+ const { type, isArray } = returnValueTypes[index];
+ acc[`value${index}`] = (isArray) ? `[${type}!]!` : `${type}!`;
+ return acc;
+ }, {});
+
+ const capitalizedFunctionName = `${functionName.charAt(0).toUpperCase()}${functionName.slice(1)}`;
+
+ this._composer.getOrCreateOTC(
+ `${capitalizedFunctionName}Type`,
+ (tc) => {
+ tc.addFields(returnValueTypesMap);
+ }
+ );
+
+ objectTCName = objectTCName.concat(`${capitalizedFunctionName}Type`);
+ value = `${capitalizedFunctionName}Type!`;
+ } else {
+ const { type, isArray } = returnValueTypes[0];
+ value = (isArray) ? `[${type}!]!` : `${type}!`;
+ objectTCName = objectTCName.concat(type);
+
+ if (isArray) {
+ objectTCName = objectTCName.concat('Array');
+ }
}
const typeComposer = this._composer.getOrCreateOTC(
objectTCName,
(tc) => {
tc.addFields({
- value: (isArray) ? `[${value}]!` : value,
+ value,
proof: () => this._composer.getOTC('Proof')
});
}
diff --git a/packages/codegen/src/templates/database-template.handlebars b/packages/codegen/src/templates/database-template.handlebars
index 86bd860b..413747b1 100644
--- a/packages/codegen/src/templates/database-template.handlebars
+++ b/packages/codegen/src/templates/database-template.handlebars
@@ -37,7 +37,7 @@ export const SUBGRAPH_ENTITIES = new Set([
{{~/each}}]);
{{/if}}
export const ENTITIES = [
- {{~#each queries as | query |}}{{query.entityName}}{{#unless @last}}, {{/unless}}{{/each}}{{#if (subgraphPath)}}, {{/if}}
+ {{~#each queries as | query |}}{{query.entityName}}{{#if @last}}{{#if (subgraphPath)}}, {{/if}}{{else}}, {{/if}}{{/each}}
{{~#if (subgraphPath)}}...SUBGRAPH_ENTITIES{{/if}}];
{{#if (subgraphPath)}}
// Map: Entity to suitable query type.
diff --git a/packages/codegen/src/templates/indexer-template.handlebars b/packages/codegen/src/templates/indexer-template.handlebars
index 2e804a4e..e453159b 100644
--- a/packages/codegen/src/templates/indexer-template.handlebars
+++ b/packages/codegen/src/templates/indexer-template.handlebars
@@ -201,22 +201,34 @@ export class Indexer implements IndexerInterface {
assert(abi);
const contract = new ethers.Contract(contractAddress, abi, this._ethProvider);
- {{#if (compare query.returnType 'bigint')}}
- let value = await contract.{{query.name}}(
+ const contractResult = await contract.{{query.name}}(
{{~#each query.params}}{{this.name}}, {{/each}}{ blockTag: blockHash });
- value = value.toString();
- value = BigInt(value);
+
+ {{#if (compare query.returnTypes.length 1 operator=">")}}
+ const value = {
+ {{#each query.returnTypes as |returnType index|}}
+ {{#if (compare returnType 'bigint')}}
+ value{{index}}: ethers.BigNumber.from(contractResult[{{index}}]).toBigInt()
+ {{~else}}
+ {{!-- https://github.com/handlebars-lang/handlebars.js/issues/1716 --}}
+ {{#if (compare returnType 'bigint[]')}}
+ value{{index}}: contractResult[{{index}}].map((val: ethers.BigNumber | number) => ethers.BigNumber.from(val).toBigInt())
+ {{~else}}
+ value{{index}}: contractResult[{{index}}]
+ {{~/if}}
+ {{/if}}
+ {{~#unless @last}},{{/unless}}
+ {{/each}}
+ };
{{else}}
- {{!-- Using nested if-else to avoid indentation issue --}}
- {{#if (compare query.returnType 'bigint[]')}}
- let value = await contract.{{query.name}}(
- {{~#each query.params}}{{this.name}}, {{/each}}{ blockTag: blockHash });
- value = value.map((val: ethers.BigNumber) => ethers.BigNumber.from(val).toBigInt());
+ {{#if (compare query.returnTypes.[0] 'bigint')}}
+ const value = ethers.BigNumber.from(contractResult).toBigInt();
+ {{else if (compare query.returnTypes.[0] 'bigint[]')}}
+ const value = contractResult.map((val: ethers.BigNumber | number) => ethers.BigNumber.from(val).toBigInt());
{{else}}
- const value = await contract.{{query.name}}(
- {{~#each query.params}}{{this.name}}, {{/each}}{ blockTag: blockHash });
- {{/if}}
- {{/if}}
+ const value = contractResult;
+ {{~/if}}
+ {{~/if}}
const result: ValueResult = { value };
{{/if}}
diff --git a/packages/codegen/src/utils/helpers.ts b/packages/codegen/src/utils/helpers.ts
index 857afb9a..6bae01ee 100644
--- a/packages/codegen/src/utils/helpers.ts
+++ b/packages/codegen/src/utils/helpers.ts
@@ -4,14 +4,14 @@
import fs from 'fs';
import { Writable } from 'stream';
+import { TypeName } from '@solidity-parser/parser/dist/src/ast-types';
-const isElementaryType = (typeName: any): boolean => (typeName.type === 'ElementaryTypeName');
-export const isArrayType = (typeName: any): boolean => (typeName.type === 'ArrayTypeName');
+export const isArrayType = (typeName: TypeName): boolean => (typeName.type === 'ArrayTypeName');
-export const getBaseType = (typeName: any): string | undefined => {
- if (isElementaryType(typeName)) {
+export const getBaseType = (typeName: TypeName): string | undefined => {
+ if (typeName.type === 'ElementaryTypeName') {
return typeName.name;
- } else if (isArrayType(typeName)) {
+ } else if (typeName.type === 'ArrayTypeName') {
return getBaseType(typeName.baseTypeName);
} else {
return undefined;
diff --git a/packages/codegen/src/visitor.ts b/packages/codegen/src/visitor.ts
index 28bd3890..3fb1f665 100644
--- a/packages/codegen/src/visitor.ts
+++ b/packages/codegen/src/visitor.ts
@@ -5,6 +5,7 @@
import { Writable } from 'stream';
import assert from 'assert';
import { utils } from 'ethers';
+import { FunctionDefinition, StateVariableDeclaration } from '@solidity-parser/parser/dist/src/ast-types';
import { Database } from './database';
import { Entity } from './entity';
@@ -51,51 +52,58 @@ export class Visitor {
* Visitor function for function definitions.
* @param node ASTNode for a function definition.
*/
- functionDefinitionVisitor (node: any): void {
- if (node.stateMutability === 'view' && (node.visibility === 'external' || node.visibility === 'public')) {
- const name = node.name;
- const params = node.parameters.map((item: any) => {
- return { name: item.name, type: item.typeName.name };
- });
+ functionDefinitionVisitor (node: FunctionDefinition): void {
+ if (node.stateMutability !== 'view' || !(node.visibility === 'external' || node.visibility === 'public')) {
+ return;
+ }
- let errorMessage = '';
+ // If function doesn't return anything skip creating watcher query
+ if (!node.returnParameters) {
+ return;
+ }
- if (node.returnParameters.length > 1) {
- errorMessage = `No support in codegen for multiple returned values from method ${node.name}`;
- } else {
- const typeName = node.returnParameters[0].typeName;
- switch (typeName.type) {
- case 'ElementaryTypeName':
- this._entity.addQuery(name, params, typeName);
- this._database.addQuery(name, params, typeName);
- this._client.addQuery(name, params, typeName);
- // falls through
+ const name = node.name;
+ assert(name);
- case 'ArrayTypeName':
- this._schema.addQuery(name, params, typeName);
- this._resolvers.addQuery(name, params, typeName);
+ const params = node.parameters.map((item: any) => {
+ return { name: item.name, type: item.typeName.name };
+ });
- assert(this._contract);
- this._indexer.addQuery(this._contract.name, MODE_ETH_CALL, name, params, typeName);
- break;
+ let errorMessage = '';
- case 'UserDefinedTypeName':
- errorMessage = `No support in codegen for user defined return type from method "${node.name}"`;
- break;
+ const typeName = node.returnParameters[0].typeName;
+ assert(typeName);
- default:
- errorMessage = `No support in codegen for return type "${typeName.type}" from method "${node.name}"`;
- }
+ switch (typeName.type) {
+ case 'ElementaryTypeName':
+ this._entity.addQuery(name, params, typeName);
+ this._database.addQuery(name, params, typeName);
+ this._client.addQuery(name, params, typeName);
+ // falls through
+
+ case 'ArrayTypeName':
+ this._schema.addQuery(name, params, node.returnParameters);
+ this._resolvers.addQuery(name, params);
+
+ assert(this._contract);
+ this._indexer.addQuery(this._contract.name, MODE_ETH_CALL, name, params, node.returnParameters);
+ break;
+
+ case 'UserDefinedTypeName':
+ errorMessage = `No support in codegen for user defined return type from method "${node.name}"`;
+ break;
+
+ default:
+ errorMessage = `No support in codegen for return type "${typeName.type}" from method "${node.name}"`;
+ }
+
+ if (errorMessage !== '') {
+ if (this._continueOnError) {
+ console.log(errorMessage);
+ return;
}
- if (errorMessage !== '') {
- if (this._continueOnError) {
- console.log(errorMessage);
- return;
- }
-
- throw new Error(errorMessage);
- }
+ throw new Error(errorMessage);
}
}
@@ -103,12 +111,14 @@ export class Visitor {
* Visitor function for state variable declarations.
* @param node ASTNode for a state variable declaration.
*/
- stateVariableDeclarationVisitor (node: any): void {
+ stateVariableDeclarationVisitor (node: StateVariableDeclaration): void {
// TODO Handle multiples variables in a single line.
// TODO Handle array types.
// TODO Handle user defined type .
const variable = node.variables[0];
+ assert(variable.name);
const name: string = variable.name;
+ assert(variable.typeName);
const stateVariableType: string = variable.typeName.type;
const params: Param[] = [];
@@ -118,38 +128,43 @@ export class Visitor {
}
let typeName = variable.typeName;
- let numParams = 0;
-
- // If the variable type is mapping, extract key as a param:
- // Eg. mapping(address => mapping(address => uint256)) private _allowances;
- while (typeName.type === 'Mapping') {
- params.push({ name: `key${numParams.toString()}`, type: typeName.keyType.name });
- typeName = typeName.valueType;
- numParams++;
- }
-
let errorMessage = '';
switch (typeName.type) {
+ case 'Mapping': {
+ let numParams = 0;
+
+ // If the variable type is mapping, extract key as a param:
+ // Eg. mapping(address => mapping(address => uint256)) private _allowances;
+ while (typeName.type === 'Mapping') {
+ assert(typeName.keyType.type === 'ElementaryTypeName', 'UserDefinedTypeName map keys like enum type not handled');
+ params.push({ name: `key${numParams.toString()}`, type: typeName.keyType.name });
+ typeName = typeName.valueType;
+ numParams++;
+ }
+
+ // falls through
+ }
+
case 'ElementaryTypeName': {
- this._schema.addQuery(name, params, typeName);
- this._resolvers.addQuery(name, params, typeName);
+ this._schema.addQuery(name, params, [variable]);
+ this._resolvers.addQuery(name, params);
this._entity.addQuery(name, params, typeName);
this._database.addQuery(name, params, typeName);
this._client.addQuery(name, params, typeName);
assert(this._contract);
- this._indexer.addQuery(this._contract.name, MODE_STORAGE, name, params, typeName, stateVariableType);
+ this._indexer.addQuery(this._contract.name, MODE_STORAGE, name, params, [variable], stateVariableType);
break;
}
case 'UserDefinedTypeName':
- errorMessage = `No support in codegen for user defined return type from method "${name}"`;
+ errorMessage = `No support in codegen for user defined type state variable "${name}"`;
break;
case 'ArrayTypeName':
- errorMessage = `No support in codegen for return type "${typeName.baseTypeName.name}[]" from method "${name}"`;
+ errorMessage = `No support in codegen for array type state variable "${name}"`;
break;
default: