@graphprotocol/graph-cli 0.22.2 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/examples/basic-event-handlers/yarn.lock +4 -4
  2. package/examples/example-subgraph/yarn.lock +4 -4
  3. package/examples/near/.gitkeep +0 -0
  4. package/manifest-schema.graphql +35 -2
  5. package/package.json +1 -1
  6. package/src/codegen/schema.js +1 -2
  7. package/src/codegen/template.js +9 -51
  8. package/src/codegen/typescript.js +7 -0
  9. package/src/command-helpers/compiler.js +6 -2
  10. package/src/command-helpers/data-sources.js +38 -0
  11. package/src/command-helpers/fs.js +8 -0
  12. package/src/commands/build.js +14 -0
  13. package/src/commands/codegen.js +8 -0
  14. package/src/commands/deploy.js +11 -8
  15. package/src/commands/init.js +5 -8
  16. package/src/commands/test.js +8 -3
  17. package/src/compiler/index.js +93 -76
  18. package/src/{abi.js → protocols/ethereum/abi.js} +0 -0
  19. package/src/{codegen → protocols/ethereum/codegen}/abi.js +47 -40
  20. package/src/{codegen → protocols/ethereum/codegen}/abi.test.js +1 -1
  21. package/src/protocols/ethereum/codegen/template.js +42 -0
  22. package/src/protocols/ethereum/subgraph.js +205 -0
  23. package/src/protocols/ethereum/type-generator.js +203 -0
  24. package/src/protocols/index.js +97 -0
  25. package/src/protocols/near/subgraph.js +42 -0
  26. package/src/scaffold.js +3 -2
  27. package/src/scaffold.test.js +1 -1
  28. package/src/subgraph.js +30 -252
  29. package/src/type-generator.js +31 -211
  30. package/src/validation/index.js +1 -0
  31. package/src/validation/manifest.js +73 -23
  32. package/src/validation/schema.js +6 -0
  33. package/subgraph-ethereum.yaml +134 -0
  34. package/subgraph-near.yaml +134 -0
  35. package/tests/cli/init/from-contract/abis/Contract.json +69 -0
  36. package/tests/cli/init/from-contract/package.json +16 -0
  37. package/tests/cli/init/from-contract/schema.graphql +6 -0
  38. package/tests/cli/init/from-contract/src/mapping.ts +49 -0
  39. package/tests/cli/init/from-contract/subgraph.yaml +26 -0
  40. package/tests/cli/validation/near-is-valid/mapping.ts +0 -0
  41. package/tests/cli/validation/near-is-valid/schema.graphql +8 -0
  42. package/tests/cli/validation/near-is-valid/subgraph.yaml +21 -0
  43. package/tests/cli/validation.test.js +8 -0
@@ -9,7 +9,6 @@ const toolbox = require('gluegun/toolbox')
9
9
  const { step, withSpinner } = require('../command-helpers/spinner')
10
10
  const Subgraph = require('../subgraph')
11
11
  const Watcher = require('../watcher')
12
- const ABI = require('../abi')
13
12
  const { applyMigrations } = require('../migrations')
14
13
  const asc = require('./asc')
15
14
 
@@ -52,6 +51,9 @@ class Compiler {
52
51
 
53
52
  this.globalsFile = path.join(globalsLib, globalsFile)
54
53
 
54
+ this.protocol = this.options.protocol
55
+ this.ABI = this.protocol.getABI()
56
+
55
57
  process.on('uncaughtException', function(e) {
56
58
  toolbox.print.error(`UNCAUGHT EXCEPTION: ${e}`)
57
59
  })
@@ -104,8 +106,10 @@ class Compiler {
104
106
  }
105
107
 
106
108
  async loadSubgraph({ quiet } = { quiet: false }) {
109
+ const subgraphLoadOptions = { protocol: this.protocol, skipValidation: false }
110
+
107
111
  if (quiet) {
108
- return Subgraph.load(this.options.subgraphManifest).result
112
+ return Subgraph.load(this.options.subgraphManifest, subgraphLoadOptions).result
109
113
  } else {
110
114
  const manifestPath = this.displayPath(this.options.subgraphManifest)
111
115
 
@@ -114,7 +118,7 @@ class Compiler {
114
118
  `Failed to load subgraph from ${manifestPath}`,
115
119
  `Warnings loading subgraph from ${manifestPath}`,
116
120
  async spinner => {
117
- return Subgraph.load(this.options.subgraphManifest)
121
+ return Subgraph.load(this.options.subgraphManifest, subgraphLoadOptions)
118
122
  },
119
123
  )
120
124
  }
@@ -132,9 +136,12 @@ class Compiler {
132
136
  files.push(path.resolve(subgraph.getIn(['schema', 'file'])))
133
137
  subgraph.get('dataSources').map(dataSource => {
134
138
  files.push(dataSource.getIn(['mapping', 'file']))
135
- dataSource.getIn(['mapping', 'abis']).map(abi => {
136
- files.push(abi.get('file'))
137
- })
139
+ // Only watch ABI related files if the target protocol has support/need for them.
140
+ if (this.protocol.hasABIs()) {
141
+ dataSource.getIn(['mapping', 'abis']).map(abi => {
142
+ files.push(abi.get('file'))
143
+ })
144
+ }
138
145
  })
139
146
 
140
147
  // Make paths absolute
@@ -243,7 +250,7 @@ class Compiler {
243
250
  let baseDir = this.sourceDir
244
251
  let absoluteMappingPath = path.resolve(baseDir, mappingPath)
245
252
  let inputFile = path.relative(baseDir, absoluteMappingPath)
246
- this._validateMappingContent(inputFile)
253
+ this._validateMappingContent(absoluteMappingPath)
247
254
 
248
255
  // If the file has already been compiled elsewhere, just use that output
249
256
  // file and return early
@@ -308,7 +315,7 @@ class Compiler {
308
315
  let baseDir = this.sourceDir
309
316
  let absoluteMappingPath = path.resolve(baseDir, mappingPath)
310
317
  let inputFile = path.relative(baseDir, absoluteMappingPath)
311
- this._validateMappingContent(inputFile)
318
+ this._validateMappingContent(absoluteMappingPath)
312
319
 
313
320
  // If the file has already been compiled elsewhere, just use that output
314
321
  // file and return early
@@ -368,7 +375,7 @@ class Compiler {
368
375
  }
369
376
  }
370
377
 
371
- async _validateMappingContent(filePath) {
378
+ _validateMappingContent(filePath) {
372
379
  const data = fs.readFileSync(filePath)
373
380
  if (
374
381
  this.blockIpfsMethods &&
@@ -403,52 +410,59 @@ class Compiler {
403
410
  })
404
411
 
405
412
  // Copy data source files and update their paths
406
- subgraph = subgraph.update('dataSources', dataSources => {
407
- return dataSources.map(dataSource =>
408
- dataSource
409
- // Write data source ABIs to the output directory
410
- .updateIn(['mapping', 'abis'], abis =>
411
- abis.map(abi =>
412
- abi.update('file', abiFile => {
413
- abiFile = path.resolve(this.sourceDir, abiFile)
414
- let abiData = ABI.load(abi.get('name'), abiFile)
415
- return path.relative(
416
- this.options.outputDir,
417
- this._writeSubgraphFile(
418
- abiFile,
419
- JSON.stringify(abiData.data.toJS(), null, 2),
420
- this.sourceDir,
421
- this.subgraphDir(this.options.outputDir, dataSource),
422
- spinner,
423
- ),
424
- )
425
- }),
426
- ),
427
- )
428
-
429
- // The mapping file is already being written to the output
430
- // directory by the AssemblyScript compiler
431
- .updateIn(['mapping', 'file'], mappingFile =>
432
- path.relative(
433
- this.options.outputDir,
434
- path.resolve(this.sourceDir, mappingFile),
435
- ),
413
+ subgraph = subgraph.update('dataSources', dataSources =>
414
+ dataSources.map(dataSource => {
415
+ let updatedDataSource = dataSource
416
+
417
+ if (this.protocol.hasABIs()) {
418
+ updatedDataSource = updatedDataSource
419
+ // Write data source ABIs to the output directory
420
+ .updateIn(['mapping', 'abis'], abis =>
421
+ abis.map(abi =>
422
+ abi.update('file', abiFile => {
423
+ abiFile = path.resolve(this.sourceDir, abiFile)
424
+ let abiData = this.ABI.load(abi.get('name'), abiFile)
425
+ return path.relative(
426
+ this.options.outputDir,
427
+ this._writeSubgraphFile(
428
+ abiFile,
429
+ JSON.stringify(abiData.data.toJS(), null, 2),
430
+ this.sourceDir,
431
+ this.subgraphDir(this.options.outputDir, dataSource),
432
+ spinner,
433
+ ),
434
+ )
435
+ }),
436
+ ),
437
+ )
438
+ }
439
+
440
+ // The mapping file is already being written to the output
441
+ // directory by the AssemblyScript compiler
442
+ return updatedDataSource.updateIn(['mapping', 'file'], mappingFile =>
443
+ path.relative(
444
+ this.options.outputDir,
445
+ path.resolve(this.sourceDir, mappingFile),
436
446
  ),
437
- )
438
- })
447
+ )
448
+ })
449
+ )
439
450
 
440
451
  // Copy template files and update their paths
441
- subgraph = subgraph.update('templates', templates => {
442
- return templates === undefined
452
+ subgraph = subgraph.update('templates', templates =>
453
+ templates === undefined
443
454
  ? templates
444
- : templates.map(template =>
445
- template
455
+ : templates.map(template => {
456
+ let updatedTemplate = template
457
+
458
+ if (this.protocol.hasABIs()) {
459
+ updatedTemplate = updatedTemplate
446
460
  // Write template ABIs to the output directory
447
461
  .updateIn(['mapping', 'abis'], abis =>
448
462
  abis.map(abi =>
449
463
  abi.update('file', abiFile => {
450
464
  abiFile = path.resolve(this.sourceDir, abiFile)
451
- let abiData = ABI.load(abi.get('name'), abiFile)
465
+ let abiData = this.ABI.load(abi.get('name'), abiFile)
452
466
  return path.relative(
453
467
  this.options.outputDir,
454
468
  this._writeSubgraphFile(
@@ -462,17 +476,18 @@ class Compiler {
462
476
  }),
463
477
  ),
464
478
  )
479
+ }
465
480
 
466
- // The mapping file is already being written to the output
467
- // directory by the AssemblyScript compiler
468
- .updateIn(['mapping', 'file'], mappingFile =>
469
- path.relative(
470
- this.options.outputDir,
471
- path.resolve(this.sourceDir, mappingFile),
472
- ),
473
- ),
481
+ // The mapping file is already being written to the output
482
+ // directory by the AssemblyScript compiler
483
+ return updatedTemplate.updateIn(['mapping', 'file'], mappingFile =>
484
+ path.relative(
485
+ this.options.outputDir,
486
+ path.resolve(this.sourceDir, mappingFile),
487
+ ),
474
488
  )
475
- })
489
+ })
490
+ )
476
491
 
477
492
  // Write the subgraph manifest itself
478
493
  let outputFilename = path.join(this.options.outputDir, 'subgraph.yaml')
@@ -506,17 +521,18 @@ class Compiler {
506
521
  ),
507
522
  })
508
523
 
509
- // Upload the ABIs of all data sources to IPFS
510
- for (let [i, dataSource] of subgraph.get('dataSources').entries()) {
511
- for (let [j, abi] of dataSource.getIn(['mapping', 'abis']).entries()) {
512
- updates.push({
513
- keyPath: ['dataSources', i, 'mapping', 'abis', j, 'file'],
514
- value: await this._uploadFileToIPFS(
515
- abi.get('file'),
516
- uploadedFiles,
517
- spinner,
518
- ),
519
- })
524
+ if (this.protocol.hasABIs()) {
525
+ for (let [i, dataSource] of subgraph.get('dataSources').entries()) {
526
+ for (let [j, abi] of dataSource.getIn(['mapping', 'abis']).entries()) {
527
+ updates.push({
528
+ keyPath: ['dataSources', i, 'mapping', 'abis', j, 'file'],
529
+ value: await this._uploadFileToIPFS(
530
+ abi.get('file'),
531
+ uploadedFiles,
532
+ spinner,
533
+ ),
534
+ })
535
+ }
520
536
  }
521
537
  }
522
538
 
@@ -532,17 +548,18 @@ class Compiler {
532
548
  })
533
549
  }
534
550
 
535
- // Upload the mapping and ABIs of all data source templates
536
551
  for (let [i, template] of subgraph.get('templates', immutable.List()).entries()) {
537
- for (let [j, abi] of template.getIn(['mapping', 'abis']).entries()) {
538
- updates.push({
539
- keyPath: ['templates', i, 'mapping', 'abis', j, 'file'],
540
- value: await this._uploadFileToIPFS(
541
- abi.get('file'),
542
- uploadedFiles,
543
- spinner,
544
- ),
545
- })
552
+ if (this.protocol.hasABIs()) {
553
+ for (let [j, abi] of template.getIn(['mapping', 'abis']).entries()) {
554
+ updates.push({
555
+ keyPath: ['templates', i, 'mapping', 'abis', j, 'file'],
556
+ value: await this._uploadFileToIPFS(
557
+ abi.get('file'),
558
+ uploadedFiles,
559
+ spinner,
560
+ ),
561
+ })
562
+ }
546
563
  }
547
564
 
548
565
  updates.push({
File without changes
@@ -1,9 +1,8 @@
1
1
  const immutable = require('immutable')
2
2
 
3
- const tsCodegen = require('./typescript')
4
- const typesCodegen = require('./types')
5
- const util = require('./util')
6
- const ABI = require('../abi')
3
+ const tsCodegen = require('../../../codegen/typescript')
4
+ const typesCodegen = require('../../../codegen/types')
5
+ const util = require('../../../codegen/util')
7
6
 
8
7
  module.exports = class AbiCodeGenerator {
9
8
  constructor(abi) {
@@ -48,7 +47,7 @@ module.exports = class AbiCodeGenerator {
48
47
  setName: (fn, name) => fn.set('_alias', name),
49
48
  })
50
49
 
51
- return callFunctions
50
+ callFunctions = callFunctions
52
51
  .map(fn => {
53
52
  let fnAlias = fn.get('_alias')
54
53
  let fnClassName = `${fnAlias.charAt(0).toUpperCase()}${fnAlias.slice(1)}Call`
@@ -141,6 +140,8 @@ module.exports = class AbiCodeGenerator {
141
140
  )
142
141
  return [klass, inputsClass, outputsClass, ...tupleClasses]
143
142
  })
143
+
144
+ return callFunctions
144
145
  .reduce(
145
146
  // flatten the array
146
147
  (array, classes) => array.concat(classes),
@@ -156,7 +157,7 @@ module.exports = class AbiCodeGenerator {
156
157
  setName: (event, name) => event.set('_alias', name),
157
158
  })
158
159
 
159
- return events
160
+ events = events
160
161
  .map(event => {
161
162
  let eventClassName = event.get('_alias')
162
163
  let tupleClasses = []
@@ -209,6 +210,8 @@ module.exports = class AbiCodeGenerator {
209
210
  )
210
211
  return [klass, paramsClass, ...tupleClasses]
211
212
  })
213
+
214
+ return events
212
215
  .reduce(
213
216
  // flatten the array
214
217
  (array, classes) => array.concat(classes),
@@ -274,8 +277,8 @@ module.exports = class AbiCodeGenerator {
274
277
  let isTupleType = util.isTupleType(type)
275
278
  let returnValue = typesCodegen.ethereumToAsc(
276
279
  parentType === 'tuple'
277
- ? `this[${index}]`
278
- : `this._${parentType}.${parentField}[${index}].value`,
280
+ ? `this[${index}]`
281
+ : `this._${parentType}.${parentField}[${index}].value`,
279
282
  type,
280
283
  tupleClassName,
281
284
  )
@@ -286,7 +289,9 @@ module.exports = class AbiCodeGenerator {
286
289
  [],
287
290
  util.isTupleArrayType(type) ? `Array<${tupleClassName}>` : tupleClassName,
288
291
  `
289
- return ${isTupleType ? `changetype<${tupleClassName}>(${returnValue})` : `${returnValue}`}
292
+ return ${
293
+ isTupleType ? `changetype<${tupleClassName}>(${returnValue})` : `${returnValue}`
294
+ }
290
295
  `,
291
296
  )
292
297
 
@@ -510,8 +515,8 @@ module.exports = class AbiCodeGenerator {
510
515
  : ''
511
516
  }]`
512
517
 
513
- let methodCallBody = isTry =>
514
- `
518
+ let methodCallBody = isTry => {
519
+ const methodBody = `
515
520
  ${
516
521
  isTry
517
522
  ? `
@@ -525,9 +530,9 @@ module.exports = class AbiCodeGenerator {
525
530
  let result = super.call(${superInputs})
526
531
 
527
532
  return (`
528
- }
529
- ${
530
- simpleReturnType
533
+ }`
534
+
535
+ const returnVal = simpleReturnType
531
536
  ? typesCodegen.ethereumToAsc(
532
537
  isTry ? 'value[0]' : 'result[0]',
533
538
  outputs.get(0).get('type'),
@@ -542,34 +547,36 @@ module.exports = class AbiCodeGenerator {
542
547
  )
543
548
  : `new ${returnType.name}(
544
549
  ${outputs
545
- .map(
546
- (output, index) =>
547
- `${typesCodegen.ethereumToAsc(
548
- isTry ? `value[${index}]` : `result[${index}]`,
549
- output.get('type'),
550
- util.isTupleArrayType(output.get('type'))
551
- ? this._tupleTypeName(
552
- output,
553
- index,
554
- tupleResultParentType,
555
- this.abi.name,
556
- )
557
- : '',
558
- )} ${
559
- util.isTupleType(output.get('type'))
560
- ? 'as ' +
561
- this._tupleTypeName(
562
- output,
563
- index,
564
- tupleResultParentType,
565
- this.abi.name,
566
- )
567
- : ''
568
- }`,
569
- )
550
+ .map((output, index) => {
551
+ const val = typesCodegen.ethereumToAsc(
552
+ isTry ? `value[${index}]` : `result[${index}]`,
553
+ output.get('type'),
554
+ util.isTupleArrayType(output.get('type'))
555
+ ? this._tupleTypeName(
556
+ output,
557
+ index,
558
+ tupleResultParentType,
559
+ this.abi.name,
560
+ )
561
+ : '',
562
+ )
563
+ return util.isTupleType(output.get('type'))
564
+ ? `changetype<${this._tupleTypeName(
565
+ output,
566
+ index,
567
+ tupleResultParentType,
568
+ this.abi.name,
569
+ )}>(${val})`
570
+ : val
571
+ })
570
572
  .join(', ')}
571
573
  )`
572
- } ${util.isTupleType(outputs.get(0).get('type')) ? 'as ' + returnType : ''} )`
574
+
575
+ const isTuple = util.isTupleType(outputs.get(0).get('type'))
576
+ return `${methodBody} ${
577
+ isTuple ? `changetype<${returnType}>(${returnVal})` : returnVal
578
+ })`
579
+ }
573
580
 
574
581
  // Generate method with an without `try_`.
575
582
  klass.addMethod(
@@ -3,7 +3,7 @@ const path = require('path')
3
3
  const immutable = require('immutable')
4
4
 
5
5
  const ABI = require('../abi')
6
- const ts = require('./typescript')
6
+ const ts = require('../../../codegen/typescript')
7
7
  const AbiCodeGenerator = require('./abi')
8
8
 
9
9
  let tempdir
@@ -0,0 +1,42 @@
1
+ const tsCodegen = require('../../../codegen/typescript')
2
+
3
+ module.exports = class EthereumTemplateCodeGen {
4
+ constructor(template) {
5
+ this.template = template
6
+ }
7
+
8
+ generateModuleImports() {
9
+ return [
10
+ 'Address',
11
+ ]
12
+ }
13
+
14
+ generateCreateMethod() {
15
+ const name = this.template.get('name')
16
+
17
+ return tsCodegen.staticMethod(
18
+ 'create',
19
+ [tsCodegen.param('address', tsCodegen.namedType('Address'))],
20
+ tsCodegen.namedType('void'),
21
+ `
22
+ DataSourceTemplate.create('${name}', [address.toHex()])
23
+ `,
24
+ )
25
+ }
26
+
27
+ generateCreateWithContextMethod() {
28
+ const name = this.template.get('name')
29
+
30
+ return tsCodegen.staticMethod(
31
+ 'createWithContext',
32
+ [
33
+ tsCodegen.param('address', tsCodegen.namedType('Address')),
34
+ tsCodegen.param('context', tsCodegen.namedType('DataSourceContext')),
35
+ ],
36
+ tsCodegen.namedType('void'),
37
+ `
38
+ DataSourceTemplate.createWithContext('${name}', [address.toHex()], context)
39
+ `,
40
+ )
41
+ }
42
+ }
@@ -0,0 +1,205 @@
1
+ const immutable = require('immutable')
2
+ const ABI = require('./abi')
3
+ const DataSourcesExtractor = require('../../command-helpers/data-sources')
4
+ const { validateContractValues } = require('../../validation')
5
+
6
+ module.exports = class EthereumSubgraph {
7
+ constructor(options = {}) {
8
+ this.manifest = options.manifest
9
+ this.resolveFile = options.resolveFile
10
+ this.protocol = options.protocol
11
+ }
12
+
13
+ validateManifest() {
14
+ return this.validateAbis()
15
+ .concat(this.validateContractAddresses())
16
+ .concat(this.validateEvents())
17
+ .concat(this.validateCallFunctions())
18
+ }
19
+
20
+ validateAbis() {
21
+ const dataSourcesAndTemplates = DataSourcesExtractor.fromManifest(this.manifest, this.protocol)
22
+
23
+ return dataSourcesAndTemplates.reduce(
24
+ (errors, dataSourceOrTemplate) =>
25
+ errors.concat(
26
+ this.validateDataSourceAbis(
27
+ dataSourceOrTemplate.get('dataSource'),
28
+ dataSourceOrTemplate.get('path'),
29
+ ),
30
+ ),
31
+ immutable.List(),
32
+ )
33
+ }
34
+
35
+ validateDataSourceAbis(dataSource, path) {
36
+ // Validate that the the "source > abi" reference of all data sources
37
+ // points to an existing ABI in the data source ABIs
38
+ let abiName = dataSource.getIn(['source', 'abi'])
39
+ let abiNames = dataSource.getIn(['mapping', 'abis']).map(abi => abi.get('name'))
40
+ let nameErrors = abiNames.includes(abiName)
41
+ ? immutable.List()
42
+ : immutable.fromJS([
43
+ {
44
+ path: [...path, 'source', 'abi'],
45
+ message: `\
46
+ ABI name '${abiName}' not found in mapping > abis.
47
+ Available ABIs:
48
+ ${abiNames
49
+ .sort()
50
+ .map(name => `- ${name}`)
51
+ .join('\n')}`,
52
+ },
53
+ ])
54
+
55
+ // Validate that all ABI files are valid
56
+ let fileErrors = dataSource
57
+ .getIn(['mapping', 'abis'])
58
+ .reduce((errors, abi, abiIndex) => {
59
+ try {
60
+ ABI.load(abi.get('name'), this.resolveFile(abi.get('file')))
61
+ return errors
62
+ } catch (e) {
63
+ return errors.push(
64
+ immutable.fromJS({
65
+ path: [...path, 'mapping', 'abis', abiIndex, 'file'],
66
+ message: e.message,
67
+ }),
68
+ )
69
+ }
70
+ }, immutable.List())
71
+
72
+ return nameErrors.concat(fileErrors)
73
+ }
74
+
75
+ validateContractAddresses() {
76
+ const ethereumAddressPattern = /^(0x)?[0-9a-fA-F]{40}$/
77
+
78
+ return validateContractValues(
79
+ this.manifest,
80
+ this.protocol,
81
+ 'address',
82
+ address => ethereumAddressPattern.test(address),
83
+ "Must be 40 hexadecimal characters, with an optional '0x' prefix.",
84
+ )
85
+ }
86
+
87
+ validateEvents() {
88
+ const dataSourcesAndTemplates = DataSourcesExtractor.fromManifest(this.manifest, this.protocol)
89
+
90
+ return dataSourcesAndTemplates
91
+ .reduce((errors, dataSourceOrTemplate) => {
92
+ return errors.concat(
93
+ this.validateDataSourceEvents(
94
+ dataSourceOrTemplate.get('dataSource'),
95
+ dataSourceOrTemplate.get('path'),
96
+ ),
97
+ )
98
+ }, immutable.List())
99
+ }
100
+
101
+ validateDataSourceEvents(dataSource, path) {
102
+ let abi
103
+ try {
104
+ // Resolve the source ABI name into a real ABI object
105
+ let abiName = dataSource.getIn(['source', 'abi'])
106
+ let abiEntry = dataSource
107
+ .getIn(['mapping', 'abis'])
108
+ .find(abi => abi.get('name') === abiName)
109
+ abi = ABI.load(abiEntry.get('name'), this.resolveFile(abiEntry.get('file')))
110
+ } catch (_) {
111
+ // Ignore errors silently; we can't really say anything about
112
+ // the events if the ABI can't even be loaded
113
+ return immutable.List()
114
+ }
115
+
116
+ // Obtain event signatures from the mapping
117
+ let manifestEvents = dataSource
118
+ .getIn(['mapping', 'eventHandlers'], immutable.List())
119
+ .map(handler => handler.get('event'))
120
+
121
+ // Obtain event signatures from the ABI
122
+ let abiEvents = abi.eventSignatures()
123
+
124
+ // Add errors for every manifest event signature that is not
125
+ // present in the ABI
126
+ return manifestEvents.reduce(
127
+ (errors, manifestEvent, index) =>
128
+ abiEvents.includes(manifestEvent)
129
+ ? errors
130
+ : errors.push(
131
+ immutable.fromJS({
132
+ path: [...path, 'eventHandlers', index],
133
+ message: `\
134
+ Event with signature '${manifestEvent}' not present in ABI '${abi.name}'.
135
+ Available events:
136
+ ${abiEvents
137
+ .sort()
138
+ .map(event => `- ${event}`)
139
+ .join('\n')}`,
140
+ }),
141
+ ),
142
+ immutable.List(),
143
+ )
144
+ }
145
+
146
+ validateCallFunctions() {
147
+ return this.manifest
148
+ .get('dataSources')
149
+ .filter(dataSource => this.protocol.isValidKindName(dataSource.get('kind')))
150
+ .reduce((errors, dataSource, dataSourceIndex) => {
151
+ let path = ['dataSources', dataSourceIndex, 'callHandlers']
152
+
153
+ let abi
154
+ try {
155
+ // Resolve the source ABI name into a real ABI object
156
+ let abiName = dataSource.getIn(['source', 'abi'])
157
+ let abiEntry = dataSource
158
+ .getIn(['mapping', 'abis'])
159
+ .find(abi => abi.get('name') === abiName)
160
+ abi = ABI.load(abiEntry.get('name'), this.resolveFile(abiEntry.get('file')))
161
+ } catch (e) {
162
+ // Ignore errors silently; we can't really say anything about
163
+ // the call functions if the ABI can't even be loaded
164
+ return errors
165
+ }
166
+
167
+ // Obtain event signatures from the mapping
168
+ let manifestFunctions = dataSource
169
+ .getIn(['mapping', 'callHandlers'], immutable.List())
170
+ .map(handler => handler.get('function'))
171
+
172
+ // Obtain event signatures from the ABI
173
+ let abiFunctions = abi.callFunctionSignatures()
174
+
175
+ // Add errors for every manifest event signature that is not
176
+ // present in the ABI
177
+ return manifestFunctions.reduce(
178
+ (errors, manifestFunction, index) =>
179
+ abiFunctions.includes(manifestFunction)
180
+ ? errors
181
+ : errors.push(
182
+ immutable.fromJS({
183
+ path: [...path, index],
184
+ message: `\
185
+ Call function with signature '${manifestFunction}' not present in ABI '${abi.name}'.
186
+ Available call functions:
187
+ ${abiFunctions
188
+ .sort()
189
+ .map(tx => `- ${tx}`)
190
+ .join('\n')}`,
191
+ }),
192
+ ),
193
+ errors,
194
+ )
195
+ }, immutable.List())
196
+ }
197
+
198
+ handlerTypes() {
199
+ return immutable.List([
200
+ 'blockHandlers',
201
+ 'callHandlers',
202
+ 'eventHandlers',
203
+ ])
204
+ }
205
+ }