-
Notifications
You must be signed in to change notification settings - Fork 212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support different input types to block handlers, validate manifest and mapping functions #516
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ let { strOptions } = require('yaml/types') | |
let graphql = require('graphql/language') | ||
let validation = require('./validation') | ||
let ABI = require('./abi') | ||
let asc = require('assemblyscript') | ||
|
||
const throwCombinedError = (filename, errors) => { | ||
throw new Error( | ||
|
@@ -23,6 +24,13 @@ const throwCombinedError = (filename, errors) => { | |
) | ||
} | ||
|
||
// Define conversions from a 'blockFormat' value in the manifest to its corresponding struct name | ||
const blockFormatToStructName = immutable.Map({ | ||
'block-only': 'Block', | ||
'block-with-transactions': 'BlockWithTransactions', | ||
'block-with-receipts': 'BlockWithReceipts', | ||
}) | ||
|
||
const buildCombinedWarning = (filename, warnings) => | ||
warnings.size > 0 | ||
? warnings.reduce( | ||
|
@@ -317,6 +325,91 @@ ${abiFunctions | |
}, immutable.List()) | ||
} | ||
|
||
static validateBlockFunctions(manifest, { resolveFile }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I forgot how hard it is to review JS code, thanks for the reminder. Does dataSource really have a Also, it's interesting to see what looks like a mature and fully-fledged persistent immutable data structure library in JS. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, |
||
return manifest | ||
.get('dataSources') | ||
.filter( | ||
dataSource => | ||
dataSource.get('kind') === 'ethereum/contract' && | ||
dataSource.getIn(['mapping', 'blockHandlers'], immutable.List()).count() > 0, | ||
) | ||
.reduce((errors, dataSource, dataSourceIndex) => { | ||
let path = ['dataSources', dataSourceIndex, 'blockHandlers'] | ||
// Use the Assemblyscript parser to generate an AST from the mapping file | ||
let mappingFile = dataSource.getIn(['mapping', 'file']) | ||
let mappingParser = new asc.Parser() | ||
mappingParser.parseFile( | ||
fs.readFileSync(resolveFile(mappingFile), 'utf-8'), | ||
'', | ||
false, | ||
) | ||
|
||
let blockHandlers = dataSource.getIn( | ||
['mapping', 'blockHandlers'], | ||
immutable.List(), | ||
) | ||
|
||
// Ensure each blockHandler has a corresponding mapping handler | ||
// with a compatible function signature and uses a supported `blockFormat` value. | ||
return errors.concat( | ||
blockHandlers.reduce( | ||
(errors, handler, index) => | ||
mappingParser.program.sources | ||
.filter(source => source.kind == asc.SourceKind.DEFAULT) | ||
.some(source => | ||
source.statements | ||
.filter( | ||
statement => statement.kind === asc.NodeKind.FUNCTIONDECLARATION, | ||
) | ||
.some( | ||
functionDeclaration => | ||
functionDeclaration.name.text === handler.get('handler') && | ||
functionDeclaration.signature.parameters.length === 1 && | ||
functionDeclaration.signature.parameters[0].type.name.identifier | ||
.text === 'ethereum' && | ||
functionDeclaration.signature.parameters[0].type.name.next | ||
.identifier.text === | ||
blockFormatToStructName.get( | ||
handler.get('blockFormat', 'block-only'), | ||
) && | ||
functionDeclaration.signature.parameters[0].type.name.next | ||
.next === null && | ||
functionDeclaration.signature.returnType.name.identifier.text === | ||
'void', | ||
), | ||
) | ||
? errors | ||
: blockFormatToStructName.get(handler.get('blockFormat', 'block-only')) | ||
? errors.push( | ||
immutable.fromJS({ | ||
path: [...path, index], | ||
message: `\ | ||
Matching mapping handler not found in '${mappingFile}' for blockHandler: '${handler.get( | ||
'handler', | ||
)}'. | ||
Signature: | ||
${handler.get('handler')}(block: ethereum.${blockFormatToStructName.get( | ||
handler.get('blockFormat', 'block-only'), | ||
)}): void`, | ||
}), | ||
) | ||
: errors.push( | ||
immutable.fromJS({ | ||
path: [...path, index], | ||
message: `Unsupported blockFormat, '${handler.get( | ||
'blockFormat', | ||
)}', specified for the '${handler.get('handler')}' blockHandler. | ||
Please use one of the supported blockFormats: ${JSON.stringify( | ||
blockFormatToStructName.keySeq(), | ||
)}`, | ||
}), | ||
), | ||
immutable.List(), | ||
), | ||
) | ||
}, immutable.List()) | ||
} | ||
|
||
static validateRepository(manifest, { resolveFile }) { | ||
return manifest.get('repository') !== | ||
'https://github.com/graphprotocol/example-subgraph' | ||
|
@@ -446,6 +539,7 @@ More than one template named '${name}', template names must be unique.`, | |
...Subgraph.validateEthereumContractHandlers(manifest), | ||
...Subgraph.validateEvents(manifest, { resolveFile }), | ||
...Subgraph.validateCallFunctions(manifest, { resolveFile }), | ||
...Subgraph.validateBlockFunctions(manifest, { resolveFile }), | ||
...Subgraph.validateUniqueDataSourceNames(manifest), | ||
...Subgraph.validateUniqueTemplateNames(manifest), | ||
) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
- Load subgraph from subgraph.yaml | ||
✖ Failed to load subgraph from subgraph.yaml: Error in subgraph.yaml: | ||
|
||
Path: dataSources > 0 > blockHandlers > 0 | ||
Matching mapping handler not found in './mapping.ts' for blockHandler: 'handleBlock'. | ||
Signature: | ||
handleBlock(block: ethereum.BlockWithTransactions): void |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[ | ||
{ | ||
"type": "event", | ||
"name": "ExampleEvent", | ||
"inputs": [{ "type": "string" }] | ||
} | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { ethereum } from '@graphprotocol/graph-ts' | ||
import { ExampleBlockEntity } from './generated/schema' | ||
|
||
export function handleBlock(block: ethereum.BlockWithReceipts): void { | ||
let entity = new ExampleBlockEntity(block.hash.toHexString()) | ||
entity.number = block.number | ||
entity.hash = block.hash | ||
entity.save() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
type ExampleBlockEntity @entity { | ||
id: ID! | ||
number: BigInt! | ||
hash: Bytes! | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
specVersion: 0.0.1 | ||
schema: | ||
file: ./schema.graphql | ||
dataSources: | ||
- kind: ethereum/contract | ||
name: ExampleSubgraph | ||
network: mainnet | ||
source: | ||
abi: ExampleContract | ||
mapping: | ||
kind: ethereum/events | ||
apiVersion: 0.0.1 | ||
language: wasm/assemblyscript | ||
file: ./mapping.ts | ||
entities: | ||
- ExampleBlockEntity | ||
abis: | ||
- name: ExampleContract | ||
file: ./Abi.json | ||
blockHandlers: | ||
- handler: handleBlock | ||
blockFormat: block-with-transactions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we now have a separation of naming convention between
blockFormat
values in the manifest and their corresponding struct names, I've included a mapping between the two here. This gets used invalidateBlockFunctions()
to ensure the data structure used in the mapping matches what was defined in the manifest.