@graphprotocol/graph-cli 0.26.0 → 0.28.1
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.
- package/README.md +6 -6
- package/examples/basic-event-handlers/node_modules/@graphprotocol/graph-ts/chain/ethereum.ts +1 -1
- package/examples/basic-event-handlers/node_modules/@graphprotocol/graph-ts/chain/tendermint.ts +554 -0
- package/examples/basic-event-handlers/node_modules/@graphprotocol/graph-ts/common/collections.ts +52 -2
- package/examples/basic-event-handlers/node_modules/@graphprotocol/graph-ts/common/numbers.ts +11 -0
- package/examples/basic-event-handlers/node_modules/@graphprotocol/graph-ts/common/value.ts +47 -0
- package/examples/basic-event-handlers/node_modules/@graphprotocol/graph-ts/global/global.ts +150 -1
- package/examples/basic-event-handlers/node_modules/@graphprotocol/graph-ts/index.ts +2 -0
- package/examples/basic-event-handlers/node_modules/@graphprotocol/graph-ts/package.json +2 -1
- package/examples/basic-event-handlers/node_modules/@graphprotocol/graph-ts/test/test.js +2 -0
- package/examples/basic-event-handlers/package.json +5 -5
- package/examples/basic-event-handlers/yarn.lock +12 -12
- package/examples/example-subgraph/package.json +1 -1
- package/examples/example-subgraph/yarn.lock +4 -4
- package/package.json +27 -24
- package/src/codegen/schema.js +104 -25
- package/src/codegen/schema.test.js +3 -7
- package/src/commands/codegen.js +1 -1
- package/src/commands/deploy.js +4 -2
- package/src/commands/init.js +3 -0
- package/src/commands/test.js +82 -59
- package/src/protocols/ethereum/codegen/abi.js +72 -2
- package/src/protocols/ethereum/manifest.graphql +92 -0
- package/src/protocols/index.js +51 -7
- package/src/protocols/near/manifest.graphql +57 -0
- package/src/protocols/tendermint/manifest.graphql +64 -0
- package/src/protocols/tendermint/subgraph.js +20 -0
- package/src/scaffold/index.js +1 -1
- package/src/subgraph.js +22 -13
- package/src/validation/manifest.js +29 -8
- package/src/validation/schema.js +120 -119
- package/tests/cli/validation/example-values-found.stderr +2 -2
- package/tests/cli/validation/invalid-manifest/subgraph.yaml +2 -1
- package/tests/cli/validation/invalid-manifest-cannot-infer-protocol/subgraph.yaml +12 -0
- package/tests/cli/validation/invalid-manifest-cannot-infer-protocol.stderr +5 -0
- package/tests/cli/validation/invalid-manifest.stderr +0 -3
- package/tests/cli/validation.test.js +8 -0
- package/manifest-schema.graphql +0 -122
package/src/codegen/schema.js
CHANGED
|
@@ -5,6 +5,53 @@ const typesCodegen = require('./types')
|
|
|
5
5
|
|
|
6
6
|
const List = immutable.List
|
|
7
7
|
|
|
8
|
+
class IdField {
|
|
9
|
+
static BYTES = Symbol("Bytes")
|
|
10
|
+
static STRING = Symbol("String")
|
|
11
|
+
|
|
12
|
+
constructor(idField) {
|
|
13
|
+
const typeName = idField.getIn(['type', 'type', 'name', 'value'])
|
|
14
|
+
this.kind = typeName === "Bytes" ? IdField.BYTES : IdField.STRING
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
typeName() {
|
|
18
|
+
return this.kind === IdField.BYTES ? "Bytes" : "string"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
gqlTypeName() {
|
|
22
|
+
return this.kind === IdField.BYTES ? "Bytes" : "String"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
tsNamedType() {
|
|
26
|
+
return tsCodegen.namedType(this.typeName())
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
tsValueFrom() {
|
|
30
|
+
return this.kind === IdField.BYTES ? "Value.fromBytes(id)" : "Value.fromString(id)"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
tsValueKind() {
|
|
34
|
+
return this.kind === IdField.BYTES ? "ValueKind.BYTES" : "ValueKind.STRING"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
tsValueToString() {
|
|
38
|
+
return this.kind == IdField.BYTES ? "id.toBytes().toHexString()" : "id.toString()"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
tsToString() {
|
|
42
|
+
return this.kind == IdField.BYTES ? "id.toHexString()" : "id"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static fromFields(fields) {
|
|
46
|
+
const idField = fields.find(field => field.getIn(['name', 'value']) === 'id')
|
|
47
|
+
return new IdField(idField)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static fromTypeDef(def) {
|
|
51
|
+
return IdField.fromFields(def.get("fields"))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
8
55
|
module.exports = class SchemaCodeGenerator {
|
|
9
56
|
constructor(schema) {
|
|
10
57
|
this.schema = schema
|
|
@@ -52,12 +99,14 @@ module.exports = class SchemaCodeGenerator {
|
|
|
52
99
|
_generateEntityType(def) {
|
|
53
100
|
let name = def.getIn(['name', 'value'])
|
|
54
101
|
let klass = tsCodegen.klass(name, { export: true, extends: 'Entity' })
|
|
102
|
+
const fields = def.get('fields')
|
|
103
|
+
const idField = IdField.fromFields(fields)
|
|
55
104
|
|
|
56
105
|
// Generate and add a constructor
|
|
57
|
-
klass.addMethod(this._generateConstructor(name,
|
|
106
|
+
klass.addMethod(this._generateConstructor(name, fields))
|
|
58
107
|
|
|
59
108
|
// Generate and add save() and getById() methods
|
|
60
|
-
this._generateStoreMethods(name).forEach(method => klass.addMethod(method))
|
|
109
|
+
this._generateStoreMethods(name, idField).forEach(method => klass.addMethod(method))
|
|
61
110
|
|
|
62
111
|
// Generate and add entity field getters and setters
|
|
63
112
|
def
|
|
@@ -107,19 +156,20 @@ module.exports = class SchemaCodeGenerator {
|
|
|
107
156
|
}
|
|
108
157
|
|
|
109
158
|
_generateConstructor(entityName, fields) {
|
|
159
|
+
const idField = IdField.fromFields(fields)
|
|
110
160
|
return tsCodegen.method(
|
|
111
161
|
'constructor',
|
|
112
|
-
[tsCodegen.param('id',
|
|
162
|
+
[tsCodegen.param('id', idField.tsNamedType())],
|
|
113
163
|
undefined,
|
|
114
164
|
`
|
|
115
165
|
super()
|
|
116
|
-
this.set('id',
|
|
166
|
+
this.set('id', ${idField.tsValueFrom()})
|
|
117
167
|
${this._generateDefaultFieldValues(fields)}
|
|
118
168
|
`,
|
|
119
169
|
)
|
|
120
170
|
}
|
|
121
171
|
|
|
122
|
-
_generateStoreMethods(entityName) {
|
|
172
|
+
_generateStoreMethods(entityName, idField) {
|
|
123
173
|
return List.of(
|
|
124
174
|
tsCodegen.method(
|
|
125
175
|
'save',
|
|
@@ -127,23 +177,21 @@ module.exports = class SchemaCodeGenerator {
|
|
|
127
177
|
tsCodegen.namedType('void'),
|
|
128
178
|
`
|
|
129
179
|
let id = this.get('id')
|
|
130
|
-
assert(id != null,
|
|
180
|
+
assert(id != null,
|
|
181
|
+
'Cannot save ${entityName} entity without an ID')
|
|
131
182
|
if (id) {
|
|
132
|
-
assert(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
'Considering using .toHex() to convert the "id" to a string.'
|
|
136
|
-
)
|
|
137
|
-
store.set('${entityName}', id.toString(), this)
|
|
183
|
+
assert(id.kind == ${idField.tsValueKind()},
|
|
184
|
+
\`Entities of type ${entityName} must have an ID of type ${idField.gqlTypeName()} but the id '\${id.displayData()}' is of type \${id.displayKind()}\`)
|
|
185
|
+
store.set('${entityName}', ${idField.tsValueToString()}, this)
|
|
138
186
|
}`,
|
|
139
187
|
),
|
|
140
188
|
|
|
141
189
|
tsCodegen.staticMethod(
|
|
142
190
|
'load',
|
|
143
|
-
[tsCodegen.param('id', tsCodegen.namedType(
|
|
191
|
+
[tsCodegen.param('id', tsCodegen.namedType(idField.typeName()))],
|
|
144
192
|
tsCodegen.nullableType(tsCodegen.namedType(entityName)),
|
|
145
193
|
`
|
|
146
|
-
return changetype<${entityName} | null>(store.get('${entityName}',
|
|
194
|
+
return changetype<${entityName} | null>(store.get('${entityName}', ${idField.tsToString()}))
|
|
147
195
|
`,
|
|
148
196
|
),
|
|
149
197
|
)
|
|
@@ -194,13 +242,12 @@ module.exports = class SchemaCodeGenerator {
|
|
|
194
242
|
isArray &&
|
|
195
243
|
paramType.inner instanceof tsCodegen.NullableType
|
|
196
244
|
) {
|
|
197
|
-
let
|
|
198
|
-
let suggestedType = `${arrayTypeWithoutClosingBracked}!]`
|
|
245
|
+
let baseType = this._baseType(gqlType)
|
|
199
246
|
|
|
200
247
|
throw new Error(`
|
|
201
248
|
GraphQL schema can't have List's with Nullable members.
|
|
202
|
-
Error in '${name}' field of type '${
|
|
203
|
-
Suggestion: add an '!' to the member type of the List, change from '${
|
|
249
|
+
Error in '${name}' field of type '[${baseType}]'.
|
|
250
|
+
Suggestion: add an '!' to the member type of the List, change from '[${baseType}]' to '[${baseType}!]'`
|
|
204
251
|
)
|
|
205
252
|
}
|
|
206
253
|
|
|
@@ -212,7 +259,7 @@ Suggestion: add an '!' to the member type of the List, change from '${fieldValue
|
|
|
212
259
|
this.unset('${name}')
|
|
213
260
|
} else {
|
|
214
261
|
this.set('${name}', ${typesCodegen.valueFromAsc(
|
|
215
|
-
|
|
262
|
+
`<${paramTypeString}>value`,
|
|
216
263
|
fieldValueType,
|
|
217
264
|
)})
|
|
218
265
|
}
|
|
@@ -226,12 +273,44 @@ Suggestion: add an '!' to the member type of the List, change from '${fieldValue
|
|
|
226
273
|
)
|
|
227
274
|
}
|
|
228
275
|
|
|
276
|
+
_resolveFieldType(gqlType) {
|
|
277
|
+
let typeName = gqlType.getIn(['name', 'value'])
|
|
278
|
+
|
|
279
|
+
// If this is a reference to another type, the field has the type of
|
|
280
|
+
// the referred type's id field
|
|
281
|
+
const typeDef = this.schema.ast.get("definitions").
|
|
282
|
+
find(def => this._isEntityTypeDefinition(def) && def.getIn(["name", "value"]) === typeName)
|
|
283
|
+
if (typeDef) {
|
|
284
|
+
return IdField.fromTypeDef(typeDef).typeName()
|
|
285
|
+
} else {
|
|
286
|
+
return typeName
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Return the type that values for this field must have. For scalar
|
|
291
|
+
* types, that's the type from the subgraph schema. For references to
|
|
292
|
+
* other entity types, this is the same as the type of the id of the
|
|
293
|
+
* referred type, i.e., `string` or `Bytes`*/
|
|
229
294
|
_valueTypeFromGraphQl(gqlType) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
295
|
+
if (gqlType.get('kind') === 'NonNullType') {
|
|
296
|
+
return this._valueTypeFromGraphQl(gqlType.get('type'), false)
|
|
297
|
+
} else if (gqlType.get('kind') === 'ListType') {
|
|
298
|
+
return '[' + this._valueTypeFromGraphQl(gqlType.get('type')) + ']'
|
|
299
|
+
} else {
|
|
300
|
+
return this._resolveFieldType(gqlType)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Determine the base type of `gqlType` by removing any non-null
|
|
305
|
+
* constraints and using the type of elements of lists */
|
|
306
|
+
_baseType(gqlType) {
|
|
307
|
+
if (gqlType.get('kind') === 'NonNullType') {
|
|
308
|
+
return this._baseType(gqlType.get('type'))
|
|
309
|
+
} else if (gqlType.get('kind') === 'ListType') {
|
|
310
|
+
return this._baseType(gqlType.get('type'))
|
|
311
|
+
} else {
|
|
312
|
+
return gqlType.getIn(['name', 'value'])
|
|
313
|
+
}
|
|
235
314
|
}
|
|
236
315
|
|
|
237
316
|
_typeFromGraphQl(gqlType, nullable = true, nullablePrimitive = false) {
|
|
@@ -243,7 +322,7 @@ Suggestion: add an '!' to the member type of the List, change from '${fieldValue
|
|
|
243
322
|
} else {
|
|
244
323
|
// NamedType
|
|
245
324
|
let type = tsCodegen.namedType(
|
|
246
|
-
typesCodegen.ascTypeForValue(
|
|
325
|
+
typesCodegen.ascTypeForValue(this._resolveFieldType(gqlType)),
|
|
247
326
|
)
|
|
248
327
|
|
|
249
328
|
// Will not wrap primitives into NullableType by default.
|
|
@@ -129,9 +129,7 @@ describe('Schema code generator', () => {
|
|
|
129
129
|
if (id) {
|
|
130
130
|
assert(
|
|
131
131
|
id.kind == ValueKind.STRING,
|
|
132
|
-
|
|
133
|
-
'Considering using .toHex() to convert the "id" to a string.'
|
|
134
|
-
)
|
|
132
|
+
\`Entities of type Account must have an ID of type String but the id '\${id.displayData()}' is of type \${id.displayKind()}\`)
|
|
135
133
|
store.set('Account', id.toString(), this)
|
|
136
134
|
}
|
|
137
135
|
`,
|
|
@@ -279,7 +277,7 @@ describe('Schema code generator', () => {
|
|
|
279
277
|
body: `
|
|
280
278
|
super()
|
|
281
279
|
this.set('id', Value.fromString(id))
|
|
282
|
-
|
|
280
|
+
|
|
283
281
|
this.set('amount', Value.fromBigInt(BigInt.zero()))
|
|
284
282
|
this.set('account', Value.fromString(''))
|
|
285
283
|
`,
|
|
@@ -294,9 +292,7 @@ describe('Schema code generator', () => {
|
|
|
294
292
|
if (id) {
|
|
295
293
|
assert(
|
|
296
294
|
id.kind == ValueKind.STRING,
|
|
297
|
-
|
|
298
|
-
'Considering using .toHex() to convert the "id" to a string.'
|
|
299
|
-
)
|
|
295
|
+
\`Entities of type Wallet must have an ID of type String but the id '\${id.displayData()}' is of type \${id.displayKind()}\`)
|
|
300
296
|
store.set('Wallet', id.toString(), this)
|
|
301
297
|
}
|
|
302
298
|
`,
|
package/src/commands/codegen.js
CHANGED
|
@@ -71,7 +71,7 @@ module.exports = {
|
|
|
71
71
|
// because that would mean the CLI would generate code to
|
|
72
72
|
// the wrong AssemblyScript version.
|
|
73
73
|
await assertManifestApiVersion(manifest, '0.0.5')
|
|
74
|
-
await assertGraphTsVersion(path.dirname(manifest), '0.
|
|
74
|
+
await assertGraphTsVersion(path.dirname(manifest), '0.25.0')
|
|
75
75
|
|
|
76
76
|
const dataSourcesAndTemplates = await DataSourcesExtractor.fromFilePath(manifest)
|
|
77
77
|
|
package/src/commands/deploy.js
CHANGED
|
@@ -30,6 +30,7 @@ Options:
|
|
|
30
30
|
-l --version-label <label> Version label used for the deployment
|
|
31
31
|
-h, --help Show usage information
|
|
32
32
|
-i, --ipfs <node> Upload build results to an IPFS node (default: ${DEFAULT_IPFS_URL})
|
|
33
|
+
--debug-fork ID of a remote subgraph whose store will be GraphQL queried
|
|
33
34
|
-o, --output-dir <path> Output directory for build results (default: build/)
|
|
34
35
|
--skip-migrations Skip subgraph migrations (default: false)
|
|
35
36
|
-w, --watch Regenerate types when subgraph files change (default: false)
|
|
@@ -96,6 +97,7 @@ module.exports = {
|
|
|
96
97
|
skipMigrations,
|
|
97
98
|
w,
|
|
98
99
|
watch,
|
|
100
|
+
debugFork,
|
|
99
101
|
} = toolbox.parameters.options
|
|
100
102
|
|
|
101
103
|
// Support both long and short option variants
|
|
@@ -198,7 +200,7 @@ module.exports = {
|
|
|
198
200
|
// because that would mean the CLI would try to compile code
|
|
199
201
|
// using the wrong AssemblyScript compiler.
|
|
200
202
|
await assertManifestApiVersion(manifest, '0.0.5')
|
|
201
|
-
await assertGraphTsVersion(path.dirname(manifest), '0.
|
|
203
|
+
await assertGraphTsVersion(path.dirname(manifest), '0.25.0')
|
|
202
204
|
|
|
203
205
|
const dataSourcesAndTemplates = await DataSourcesExtractor.fromFilePath(manifest)
|
|
204
206
|
|
|
@@ -264,7 +266,7 @@ module.exports = {
|
|
|
264
266
|
// `Failed to deploy to Graph node ${requestUrl}`,
|
|
265
267
|
client.request(
|
|
266
268
|
'subgraph_deploy',
|
|
267
|
-
{ name: subgraphName, ipfs_hash: ipfsHash, version_label: versionLabel },
|
|
269
|
+
{ name: subgraphName, ipfs_hash: ipfsHash, version_label: versionLabel, debug_fork: debugFork },
|
|
268
270
|
async (requestError, jsonRpcError, res) => {
|
|
269
271
|
if (jsonRpcError) {
|
|
270
272
|
spinner.fail(
|
package/src/commands/init.js
CHANGED
|
@@ -283,6 +283,9 @@ const getEtherscanLikeAPIUrl = (network) => {
|
|
|
283
283
|
case "bsc": return `https://api.bscscan.com/api`;
|
|
284
284
|
case "matic": return `https://api.polygonscan.com/api`;
|
|
285
285
|
case "mumbai": return `https://api-testnet.polygonscan.com/api`;
|
|
286
|
+
case "aurora": return `https://api.aurorascan.dev/api`;
|
|
287
|
+
case "aurora-testnet": return `https://api-testnet.aurorascan.dev/api`;
|
|
288
|
+
case "optimism-kovan": return `https://api-kovan-optimistic.etherscan.io/api`;
|
|
286
289
|
default: return `https://api-${network}.etherscan.io/api`;
|
|
287
290
|
}
|
|
288
291
|
}
|
package/src/commands/test.js
CHANGED
|
@@ -2,8 +2,9 @@ const { Binary } = require('binary-install-raw')
|
|
|
2
2
|
const os = require('os')
|
|
3
3
|
const chalk = require('chalk')
|
|
4
4
|
const fetch = require('node-fetch')
|
|
5
|
-
const { filesystem, print } = require('gluegun')
|
|
5
|
+
const { filesystem, patching, print, system } = require('gluegun')
|
|
6
6
|
const { fixParameters } = require('../command-helpers/gluegun')
|
|
7
|
+
const path = require('path')
|
|
7
8
|
const semver = require('semver')
|
|
8
9
|
const { spawn, exec } = require('child_process')
|
|
9
10
|
const yaml = require('js-yaml')
|
|
@@ -102,7 +103,7 @@ async function runBinary(datasource, opts) {
|
|
|
102
103
|
let latestVersion = opts.get("latestVersion")
|
|
103
104
|
let recompileOpt = opts.get("recompile")
|
|
104
105
|
|
|
105
|
-
const platform = getPlatform(logsOpt)
|
|
106
|
+
const platform = await getPlatform(logsOpt)
|
|
106
107
|
|
|
107
108
|
const url = `https://github.com/LimeChain/matchstick/releases/download/${versionOpt || latestVersion}/${platform}`
|
|
108
109
|
|
|
@@ -120,19 +121,21 @@ async function runBinary(datasource, opts) {
|
|
|
120
121
|
args.length > 0 ? binary.run(...args) : binary.run()
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
function getPlatform(logsOpt) {
|
|
124
|
+
async function getPlatform(logsOpt) {
|
|
124
125
|
const type = os.type()
|
|
125
126
|
const arch = os.arch()
|
|
126
|
-
const release = os.release()
|
|
127
127
|
const cpuCore = os.cpus()[0]
|
|
128
|
-
const
|
|
129
|
-
const
|
|
128
|
+
const isM1 = (arch === 'arm64' && /Apple (M1|processor)/.test(cpuCore.model))
|
|
129
|
+
const linuxInfo = type === 'Linux' ? await getLinuxInfo() : new Map()
|
|
130
|
+
const linuxDistro = linuxInfo.get('name')
|
|
131
|
+
const release = linuxInfo.get('version') || os.release()
|
|
132
|
+
const majorVersion = parseInt(linuxInfo.get('version'), 10) || semver.major(release)
|
|
130
133
|
|
|
131
134
|
if (logsOpt) {
|
|
132
|
-
print.info(`OS type: ${type}\nOS arch: ${arch}\nOS release: ${release}\nOS major version: ${majorVersion}\nCPU model: ${cpuCore.model}`)
|
|
135
|
+
print.info(`OS type: ${linuxDistro || type}\nOS arch: ${arch}\nOS release: ${release}\nOS major version: ${majorVersion}\nCPU model: ${cpuCore.model}`)
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
if (arch === 'x64' ||
|
|
138
|
+
if (arch === 'x64' || isM1) {
|
|
136
139
|
if (type === 'Darwin') {
|
|
137
140
|
if (majorVersion === 19) {
|
|
138
141
|
return 'binary-macos-10.15'
|
|
@@ -145,8 +148,9 @@ function getPlatform(logsOpt) {
|
|
|
145
148
|
} else if (type === 'Linux') {
|
|
146
149
|
if (majorVersion === 18) {
|
|
147
150
|
return 'binary-linux-18'
|
|
151
|
+
} else {
|
|
152
|
+
return 'binary-linux-20'
|
|
148
153
|
}
|
|
149
|
-
return 'binary-linux-20'
|
|
150
154
|
} else if (type === 'Windows_NT') {
|
|
151
155
|
return 'binary-windows'
|
|
152
156
|
}
|
|
@@ -155,6 +159,23 @@ function getPlatform(logsOpt) {
|
|
|
155
159
|
throw new Error(`Unsupported platform: ${type} ${arch} ${majorVersion}`)
|
|
156
160
|
}
|
|
157
161
|
|
|
162
|
+
async function getLinuxInfo() {
|
|
163
|
+
try {
|
|
164
|
+
let result = await system.run("cat /etc/*-release | grep -E '(^VERSION|^NAME)='", {trim: true})
|
|
165
|
+
let infoArray = result.replace(/['"]+/g, '').split('\n').map(p => p.split('='))
|
|
166
|
+
let infoMap = new Map();
|
|
167
|
+
|
|
168
|
+
infoArray.forEach((val) => {
|
|
169
|
+
infoMap.set(val[0].toLowerCase(), val[1])
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return infoMap
|
|
173
|
+
} catch (error) {
|
|
174
|
+
print.error(`Error fetching the Linux version:\n ${error}`)
|
|
175
|
+
process.exit(1)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
158
179
|
async function runDocker(datasource, opts) {
|
|
159
180
|
let coverageOpt = opts.get("coverage")
|
|
160
181
|
let forceOpt = opts.get("force")
|
|
@@ -169,27 +190,34 @@ async function runDocker(datasource, opts) {
|
|
|
169
190
|
// Get current working directory
|
|
170
191
|
let current_folder = await filesystem.cwd()
|
|
171
192
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
let dockerDir = ""
|
|
193
|
+
// Declate dockerfilePath with default location
|
|
194
|
+
let dockerfilePath = "./tests/.docker/Dockerfile"
|
|
175
195
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
196
|
+
// Check if matchstick.yaml config exists
|
|
197
|
+
if(filesystem.exists('matchstick.yaml')) {
|
|
198
|
+
try {
|
|
199
|
+
// Load the config
|
|
200
|
+
let config = await yaml.load(filesystem.read('matchstick.yaml', 'utf8'))
|
|
201
|
+
|
|
202
|
+
// Check if matchstick.yaml is not empty
|
|
203
|
+
if(config != null) {
|
|
204
|
+
// If a custom tests folder is declared update dockerfilePath
|
|
205
|
+
dockerfilePath = path.join(config.testsFolder || 'tests', '.docker/Dockerfile')
|
|
206
|
+
}
|
|
207
|
+
} catch (error) {
|
|
208
|
+
print.info('A problem occurred while reading matchstick.yaml. Please attend to the errors below:')
|
|
209
|
+
print.error(error.message)
|
|
210
|
+
process.exit(1)
|
|
211
|
+
}
|
|
183
212
|
}
|
|
184
213
|
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return
|
|
214
|
+
// Check if the Dockerfil already exists
|
|
215
|
+
let dockerfileExists = filesystem.exists(dockerfilePath)
|
|
216
|
+
|
|
217
|
+
// Generate the Dockerfile only if it doesn't exists,
|
|
218
|
+
// version flag and/or force flag is passed.
|
|
219
|
+
if(!dockerfileExists || versionOpt || forceOpt) {
|
|
220
|
+
await dockerfile(dockerfilePath, versionOpt, latestVersion)
|
|
193
221
|
}
|
|
194
222
|
|
|
195
223
|
// Run a command to check if matchstick image already exists
|
|
@@ -212,10 +240,10 @@ async function runDocker(datasource, opts) {
|
|
|
212
240
|
|
|
213
241
|
// If a matchstick image does not exists, the command returns an empty string,
|
|
214
242
|
// else it'll return the image ID. Skip `docker build` if an image already exists
|
|
215
|
-
//
|
|
243
|
+
// Delete current image(if any) and rebuild.
|
|
216
244
|
// Use spawn() and {stdio: 'inherit'} so we can see the logs in real time.
|
|
217
|
-
if(stdout === '' || versionOpt || forceOpt) {
|
|
218
|
-
if (
|
|
245
|
+
if(!dockerfileExists || stdout === '' || versionOpt || forceOpt) {
|
|
246
|
+
if (stdout !== '') {
|
|
219
247
|
exec('docker image rm matchstick', (error, stdout, stderr) => {
|
|
220
248
|
print.info(chalk.bold(`Removing matchstick image\n${stdout}`))
|
|
221
249
|
})
|
|
@@ -224,7 +252,7 @@ async function runDocker(datasource, opts) {
|
|
|
224
252
|
// run a container from that image.
|
|
225
253
|
spawn(
|
|
226
254
|
'docker',
|
|
227
|
-
['build', '
|
|
255
|
+
['build', '-f', dockerfilePath, '-t', 'matchstick', '.'],
|
|
228
256
|
{ stdio: 'inherit' }
|
|
229
257
|
).on('close', code => {
|
|
230
258
|
if (code === 0) {
|
|
@@ -239,37 +267,32 @@ async function runDocker(datasource, opts) {
|
|
|
239
267
|
})
|
|
240
268
|
}
|
|
241
269
|
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
ENV ARGS=""
|
|
247
|
-
|
|
248
|
-
# Install necessary packages
|
|
249
|
-
RUN apt update
|
|
250
|
-
RUN apt install -y nodejs
|
|
251
|
-
RUN apt install -y npm
|
|
252
|
-
RUN apt install -y git
|
|
253
|
-
RUN apt install -y postgresql
|
|
254
|
-
RUN apt install -y curl
|
|
255
|
-
RUN apt install -y cmake
|
|
256
|
-
RUN npm install -g @graphprotocol/graph-cli
|
|
270
|
+
// Downloads Dockerfile template from the demo-subgraph repo
|
|
271
|
+
// Replaces the placeholders with their respective values
|
|
272
|
+
async function dockerfile(dockerfilePath, versionOpt, latestVersion) {
|
|
273
|
+
let spinner = print.spin("Generating Dockerfile...")
|
|
257
274
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
275
|
+
try {
|
|
276
|
+
// Fetch the Dockerfile template content from the demo-subgraph repo
|
|
277
|
+
let content = await fetch('https://raw.githubusercontent.com/LimeChain/demo-subgraph/main/Dockerfile')
|
|
278
|
+
.then((response) => {
|
|
279
|
+
if (response.ok) {
|
|
280
|
+
return response.text()
|
|
281
|
+
} else {
|
|
282
|
+
throw new Error(`Status Code: ${response.status}, with error: ${response.statusText}`);
|
|
283
|
+
}
|
|
284
|
+
})
|
|
263
285
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
WORKDIR matchstick
|
|
286
|
+
// Write the Dockerfile
|
|
287
|
+
await filesystem.write(dockerfilePath, content)
|
|
267
288
|
|
|
268
|
-
|
|
269
|
-
|
|
289
|
+
// Replaces the version placeholders
|
|
290
|
+
await patching.replace(dockerfilePath, '<MATCHSTICK_VERSION>', versionOpt || latestVersion)
|
|
270
291
|
|
|
271
|
-
|
|
272
|
-
|
|
292
|
+
} catch (error) {
|
|
293
|
+
spinner.fail(`A problem occurred while generating the Dockerfile. Please attend to the errors below:\n ${error.message}`)
|
|
294
|
+
process.exit(1)
|
|
295
|
+
}
|
|
273
296
|
|
|
274
|
-
|
|
297
|
+
spinner.succeed('Successfully generated Dockerfile.')
|
|
275
298
|
}
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
const immutable = require('immutable')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const yaml = require('yaml')
|
|
4
|
+
const request = require('sync-request')
|
|
5
|
+
const Web3EthAbi = require('web3-eth-abi');
|
|
2
6
|
|
|
3
7
|
const tsCodegen = require('../../../codegen/typescript')
|
|
4
8
|
const typesCodegen = require('../../../codegen/types')
|
|
5
9
|
const util = require('../../../codegen/util')
|
|
6
10
|
|
|
11
|
+
const doFixtureCodegen = fs.existsSync('./fixtures.yaml');
|
|
12
|
+
|
|
7
13
|
module.exports = class AbiCodeGenerator {
|
|
8
14
|
constructor(abi) {
|
|
9
15
|
this.abi = abi
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
generateModuleImports() {
|
|
13
|
-
|
|
19
|
+
let imports = [
|
|
14
20
|
tsCodegen.moduleImports(
|
|
15
21
|
[
|
|
16
22
|
// Ethereum integration
|
|
@@ -27,8 +33,21 @@ module.exports = class AbiCodeGenerator {
|
|
|
27
33
|
'BigInt',
|
|
28
34
|
],
|
|
29
35
|
'@graphprotocol/graph-ts',
|
|
30
|
-
)
|
|
36
|
+
)
|
|
31
37
|
]
|
|
38
|
+
|
|
39
|
+
if (doFixtureCodegen) {
|
|
40
|
+
imports.push(
|
|
41
|
+
tsCodegen.moduleImports(
|
|
42
|
+
[
|
|
43
|
+
'newMockEvent',
|
|
44
|
+
],
|
|
45
|
+
'matchstick-as/assembly/index',
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return imports
|
|
32
51
|
}
|
|
33
52
|
|
|
34
53
|
generateTypes() {
|
|
@@ -182,6 +201,7 @@ module.exports = class AbiCodeGenerator {
|
|
|
182
201
|
setName: (input, name) => input.set('name', name),
|
|
183
202
|
})
|
|
184
203
|
|
|
204
|
+
let namesAndTypes = []
|
|
185
205
|
inputs.forEach((input, index) => {
|
|
186
206
|
// Generate getters and classes for event params
|
|
187
207
|
let paramObject = this._generateInputOrOutput(
|
|
@@ -192,6 +212,11 @@ module.exports = class AbiCodeGenerator {
|
|
|
192
212
|
`parameters`,
|
|
193
213
|
)
|
|
194
214
|
paramsClass.addMethod(paramObject.getter)
|
|
215
|
+
let ethType = typesCodegen.ethereumTypeForAsc(paramObject.getter.returnType)
|
|
216
|
+
if (typeof ethType === typeof {} && (ethType.test("int256") || ethType.test("uint256"))) {
|
|
217
|
+
ethType = "int32"
|
|
218
|
+
}
|
|
219
|
+
namesAndTypes.push({name: paramObject.getter.name.slice(4), type: ethType})
|
|
195
220
|
tupleClasses.push(...paramObject.classes)
|
|
196
221
|
})
|
|
197
222
|
|
|
@@ -208,6 +233,51 @@ module.exports = class AbiCodeGenerator {
|
|
|
208
233
|
`return new ${paramsClassName}(this)`,
|
|
209
234
|
),
|
|
210
235
|
)
|
|
236
|
+
|
|
237
|
+
// Fixture generation
|
|
238
|
+
if (doFixtureCodegen) {
|
|
239
|
+
const args = yaml.parse(fs.readFileSync('./fixtures.yaml', 'utf8'))
|
|
240
|
+
const blockNumber = args['blockNumber']
|
|
241
|
+
const contractAddr = args['contractAddr']
|
|
242
|
+
const topic0 = args['topic0']
|
|
243
|
+
const apiKey = args['apiKey']
|
|
244
|
+
const url = `https://api.etherscan.io/api?module=logs&action=getLogs&fromBlock=${blockNumber}&toBlock=${blockNumber}&address=${contractAddr}&${topic0}=topic0&apikey=${apiKey}`;
|
|
245
|
+
|
|
246
|
+
let resp = request("GET", url)
|
|
247
|
+
let body = JSON.parse(resp.getBody("utf8"))
|
|
248
|
+
if (body.status === '0') {
|
|
249
|
+
throw new Error(body.result)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let res = Web3EthAbi.decodeLog(
|
|
253
|
+
namesAndTypes,
|
|
254
|
+
body.result[0].data,
|
|
255
|
+
[]
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
let stmnts = ""
|
|
259
|
+
for (let i = 0; i < namesAndTypes.length; i++) {
|
|
260
|
+
let code = '"' + res[i] + '"'
|
|
261
|
+
if (namesAndTypes[i].type.toString() == "address") {
|
|
262
|
+
code = `Address.fromString(${code})`
|
|
263
|
+
}
|
|
264
|
+
stmnts = stmnts.concat(`event.parameters.push(new ethereum.EventParam(\"${namesAndTypes[i].name}\", ${typesCodegen.ethereumFromAsc(code, namesAndTypes[i].type)}));`, `\n`)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
klass.addMethod(
|
|
268
|
+
tsCodegen.staticMethod(
|
|
269
|
+
`mock${eventClassName}`,
|
|
270
|
+
[],
|
|
271
|
+
tsCodegen.namedType(eventClassName),
|
|
272
|
+
`
|
|
273
|
+
let event = changetype<${eventClassName}>(newMockEvent());
|
|
274
|
+
${stmnts}
|
|
275
|
+
return event;
|
|
276
|
+
`,
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
|
|
211
281
|
return [klass, paramsClass, ...tupleClasses]
|
|
212
282
|
})
|
|
213
283
|
|