@graphprotocol/graph-cli 0.30.4 → 0.33.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.
- package/README.md +3 -2
- package/package.json +3 -3
- package/src/codegen/types/conversions.js +2 -2
- package/src/command-helpers/abi.js +5 -0
- package/src/command-helpers/compiler.js +2 -1
- package/src/command-helpers/jsonrpc.js +5 -1
- package/src/command-helpers/network.js +13 -1
- package/src/command-helpers/scaffold.js +37 -14
- package/src/commands/add.js +27 -15
- package/src/commands/build.js +2 -2
- package/src/commands/deploy.js +39 -12
- package/src/commands/init.js +88 -9
- package/src/commands/test.js +2 -6
- package/src/migrations/mapping_api_version_0_0_4.js +1 -1
- package/src/migrations/mapping_api_version_0_0_5.js +1 -1
- package/src/protocols/index.js +3 -0
- package/src/protocols/near/manifest.graphql +6 -0
- package/src/scaffold/ethereum.test.js +259 -0
- package/src/scaffold/index.js +12 -0
- package/src/scaffold/tests.js +196 -0
- package/tests/cli/validation/near-is-valid/subgraph.yaml +8 -0
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ As of today, the command line interface supports the following commands:
|
|
|
16
16
|
- `graph auth` — Stores a [Graph Node](https://github.com/graphprotocol/graph-node) access token in the system's keychain.
|
|
17
17
|
- `graph local` — Runs tests against a [Graph Node](https://github.com/graphprotocol/graph-node) test environment (using Ganache by default).
|
|
18
18
|
- `graph test` — Downloads and runs the [Matchstick](https://github.com/LimeChain/matchstick) rust binary in order to test a subgraph.
|
|
19
|
+
- `graph add` - Adds a new datasource to the yaml file and writes the necessary changes to other files - schema.graphql, abi and mapping.
|
|
19
20
|
|
|
20
21
|
## How It Works
|
|
21
22
|
|
|
@@ -49,9 +50,9 @@ yarn global add @graphprotocol/graph-cli
|
|
|
49
50
|
|
|
50
51
|
## Getting Started
|
|
51
52
|
|
|
52
|
-
The Graph CLI can be used with a local or self-hosted [Graph Node](https://github.com/graphprotocol/graph-node) or with the [Hosted Service](https://thegraph.com/explorer/). To help you get going, there are [quick start guides](https://thegraph.com/docs/quick-start) available for both.
|
|
53
|
+
The Graph CLI can be used with a local or self-hosted [Graph Node](https://github.com/graphprotocol/graph-node) or with the [Hosted Service](https://thegraph.com/explorer/). To help you get going, there are [quick start guides](https://thegraph.com/docs/en/developer/quick-start/) available for both.
|
|
53
54
|
|
|
54
|
-
If you are ready to dive into the details of building a subgraph from scratch, there is a [detailed walkthrough](https://thegraph.com/docs/
|
|
55
|
+
If you are ready to dive into the details of building a subgraph from scratch, there is a [detailed walkthrough](https://thegraph.com/docs/en/developer/create-subgraph-hosted/) for that as well, along with API documentation for the [AssemblyScript API](https://thegraph.com/docs/en/developer/assemblyscript-api/).
|
|
55
56
|
|
|
56
57
|
## Release process
|
|
57
58
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphprotocol/graph-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"license": "(Apache-2.0 OR MIT)",
|
|
5
5
|
"description": "CLI for building for and deploying to The Graph",
|
|
6
6
|
"dependencies": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"binary-install-raw": "0.0.13",
|
|
9
9
|
"chalk": "3.0.0",
|
|
10
10
|
"chokidar": "3.5.1",
|
|
11
|
-
"debug": "4.
|
|
11
|
+
"debug": "4.3.1",
|
|
12
12
|
"docker-compose": "0.23.4",
|
|
13
13
|
"dockerode": "2.5.8",
|
|
14
14
|
"fs-extra": "9.0.0",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"graphql": "15.5.0",
|
|
18
18
|
"immutable": "3.8.2",
|
|
19
19
|
"ipfs-http-client": "34.0.0",
|
|
20
|
-
"jayson": "3.
|
|
20
|
+
"jayson": "3.6.6",
|
|
21
21
|
"js-yaml": "3.13.1",
|
|
22
22
|
"node-fetch": "2.6.0",
|
|
23
23
|
"pkginfo": "0.4.1",
|
|
@@ -143,9 +143,9 @@ const ASSEMBLYSCRIPT_TO_ETHEREUM_VALUE = [
|
|
|
143
143
|
/^string\[([0-9]+)?\]$/,
|
|
144
144
|
code => `ethereum.Value.fromStringArray(${code})`,
|
|
145
145
|
],
|
|
146
|
-
['Tuple', 'tuple', code => `ethereum.Value.fromTuple(${code})`],
|
|
146
|
+
['ethereum.Tuple', 'tuple', code => `ethereum.Value.fromTuple(${code})`],
|
|
147
147
|
[
|
|
148
|
-
'Array<Tuple>',
|
|
148
|
+
'Array<ethereum.Tuple>',
|
|
149
149
|
/^tuple\[([0-9]+)?\]$/,
|
|
150
150
|
code => `ethereum.Value.fromTupleArray(${code})`,
|
|
151
151
|
],
|
|
@@ -60,6 +60,11 @@ const getEtherscanLikeAPIUrl = (network) => {
|
|
|
60
60
|
case "aurora-testnet": return `https://api-testnet.aurorascan.dev/api`
|
|
61
61
|
case "optimism-kovan": return `https://api-kovan-optimistic.etherscan.io/api`
|
|
62
62
|
case "optimism": return `https://api-optimistic.etherscan.io/api`
|
|
63
|
+
case "moonbeam": return `https://api-moonbeam.moonscan.io/api`
|
|
64
|
+
case "moonriver": return `https://api-moonriver.moonscan.io/api`
|
|
65
|
+
case "mbase": return `https://api-moonbase.moonscan.io/api`
|
|
66
|
+
case "avalanche": return `https://api.snowtrace.io/api`;
|
|
67
|
+
case "fuji": return `https://api-testnet.snowtrace.io/api`;
|
|
63
68
|
default: return `https://api-${network}.etherscan.io/api`
|
|
64
69
|
}
|
|
65
70
|
}
|
|
@@ -6,7 +6,7 @@ const Compiler = require('../compiler')
|
|
|
6
6
|
// Helper function to construct a subgraph compiler
|
|
7
7
|
const createCompiler = (
|
|
8
8
|
manifest,
|
|
9
|
-
{ ipfs, outputDir, outputFormat, skipMigrations, blockIpfsMethods, protocol }
|
|
9
|
+
{ ipfs, headers, outputDir, outputFormat, skipMigrations, blockIpfsMethods, protocol }
|
|
10
10
|
) => {
|
|
11
11
|
// Parse the IPFS URL
|
|
12
12
|
let url
|
|
@@ -25,6 +25,7 @@ The IPFS URL must be of the following format: http(s)://host[:port]/[path]`)
|
|
|
25
25
|
host: url.hostname,
|
|
26
26
|
port: url.port,
|
|
27
27
|
'api-path': url.pathname.replace(/\/$/, '') + '/api/v0/',
|
|
28
|
+
headers,
|
|
28
29
|
})
|
|
29
30
|
: undefined
|
|
30
31
|
|
|
@@ -3,12 +3,16 @@ const toolbox = require('gluegun/toolbox')
|
|
|
3
3
|
|
|
4
4
|
const createJsonRpcClient = url => {
|
|
5
5
|
let params = {
|
|
6
|
-
auth: url.auth,
|
|
7
6
|
host: url.hostname,
|
|
8
7
|
port: url.port,
|
|
9
8
|
path: url.pathname,
|
|
10
9
|
}
|
|
11
10
|
|
|
11
|
+
// username may be empty
|
|
12
|
+
if (url.password) {
|
|
13
|
+
params.auth = `${url.username}:${url.password}`
|
|
14
|
+
}
|
|
15
|
+
|
|
12
16
|
if (url.protocol === 'https:') {
|
|
13
17
|
return jayson.Client.https(params)
|
|
14
18
|
} else if (url.protocol === 'http:') {
|
|
@@ -107,7 +107,19 @@ function hasChanges(identifierName, network, networkConfig, dataSource) {
|
|
|
107
107
|
return networkChanged || addressChanged || startBlockChanged
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
const updateNetworksFile = async (toolbox, network, dataSource, address, networksFile) => {
|
|
111
|
+
await toolbox.patching.update(networksFile, (config) => {
|
|
112
|
+
if(Object.keys(config).includes(network)) {
|
|
113
|
+
Object.assign(config[network], { [dataSource]: { address } })
|
|
114
|
+
} else {
|
|
115
|
+
Object.assign(config, { [network]: { [dataSource]: { address } }})
|
|
116
|
+
}
|
|
117
|
+
return config
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
110
121
|
module.exports = {
|
|
111
122
|
updateSubgraphNetwork,
|
|
112
|
-
initNetworksConfig
|
|
123
|
+
initNetworksConfig,
|
|
124
|
+
updateNetworksFile
|
|
113
125
|
}
|
|
@@ -7,10 +7,17 @@ const { step } = require('./spinner')
|
|
|
7
7
|
const Scaffold = require('../scaffold')
|
|
8
8
|
const { generateEventIndexingHandlers } = require('../scaffold/mapping')
|
|
9
9
|
const { generateEventType, abiEvents } = require('../scaffold/schema')
|
|
10
|
+
const { generateTestsFiles } = require('../scaffold/tests')
|
|
10
11
|
const { strings } = require('gluegun')
|
|
11
12
|
const { Map } = require('immutable')
|
|
12
13
|
|
|
13
|
-
const generateDataSource = async (
|
|
14
|
+
const generateDataSource = async (
|
|
15
|
+
protocol,
|
|
16
|
+
contractName,
|
|
17
|
+
network,
|
|
18
|
+
contractAddress,
|
|
19
|
+
abi,
|
|
20
|
+
) => {
|
|
14
21
|
const protocolManifest = protocol.getManifestScaffold()
|
|
15
22
|
|
|
16
23
|
return Map.of(
|
|
@@ -89,13 +96,13 @@ const writeABI = async (abi, contractName) => {
|
|
|
89
96
|
|
|
90
97
|
const writeSchema = async (abi, protocol, schemaPath, entities) => {
|
|
91
98
|
const events = protocol.hasEvents()
|
|
92
|
-
? abiEvents(abi)
|
|
99
|
+
? abiEvents(abi)
|
|
100
|
+
.filter(event => entities.indexOf(event.get('name')) === -1)
|
|
101
|
+
.toJS()
|
|
93
102
|
: []
|
|
94
103
|
|
|
95
104
|
let data = prettier.format(
|
|
96
|
-
events.map(
|
|
97
|
-
event => generateEventType(event, protocol.name)
|
|
98
|
-
).join('\n\n'),
|
|
105
|
+
events.map(event => generateEventType(event, protocol.name)).join('\n\n'),
|
|
99
106
|
{
|
|
100
107
|
parser: 'graphql',
|
|
101
108
|
},
|
|
@@ -106,18 +113,33 @@ const writeSchema = async (abi, protocol, schemaPath, entities) => {
|
|
|
106
113
|
|
|
107
114
|
const writeMapping = async (abi, protocol, contractName, entities) => {
|
|
108
115
|
const events = protocol.hasEvents()
|
|
109
|
-
? abiEvents(abi)
|
|
116
|
+
? abiEvents(abi)
|
|
117
|
+
.filter(event => entities.indexOf(event.get('name')) === -1)
|
|
118
|
+
.toJS()
|
|
110
119
|
: []
|
|
111
120
|
|
|
112
|
-
let mapping = prettier.format(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
let mapping = prettier.format(generateEventIndexingHandlers(events, contractName), {
|
|
122
|
+
parser: 'typescript',
|
|
123
|
+
semi: false,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
await fs.writeFile(`./src/${strings.kebabCase(contractName)}.ts`, mapping, {
|
|
127
|
+
encoding: 'utf-8',
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const writeTestsFiles = async (abi, contractName) => {
|
|
132
|
+
// If a contract is added to a subgraph that has no tests folder
|
|
133
|
+
await fs.ensureDir('./tests/')
|
|
134
|
+
|
|
135
|
+
const events = abiEvents(abi).toJS()
|
|
136
|
+
const testsFiles = generateTestsFiles(contractName, events, true)
|
|
119
137
|
|
|
120
|
-
|
|
138
|
+
for (const [fileName, content] of Object.entries(testsFiles)) {
|
|
139
|
+
await fs.writeFile(`./tests/${fileName}`, content, {
|
|
140
|
+
encoding: 'utf-8',
|
|
141
|
+
})
|
|
142
|
+
}
|
|
121
143
|
}
|
|
122
144
|
|
|
123
145
|
module.exports = {
|
|
@@ -128,4 +150,5 @@ module.exports = {
|
|
|
128
150
|
writeABI,
|
|
129
151
|
writeSchema,
|
|
130
152
|
writeMapping,
|
|
153
|
+
writeTestsFiles,
|
|
131
154
|
}
|
package/src/commands/add.js
CHANGED
|
@@ -5,10 +5,11 @@ const { withSpinner } = require('../command-helpers/spinner')
|
|
|
5
5
|
const Subgraph = require('../subgraph')
|
|
6
6
|
const Protocol = require('../protocols')
|
|
7
7
|
const DataSourcesExtractor = require('../command-helpers/data-sources')
|
|
8
|
-
const { generateDataSource, writeABI, writeSchema, writeMapping } = require('../command-helpers/scaffold')
|
|
8
|
+
const { generateDataSource, writeABI, writeSchema, writeMapping, writeTestsFiles } = require('../command-helpers/scaffold')
|
|
9
9
|
const { loadAbiFromEtherscan, loadAbiFromBlockScout } = require('../command-helpers/abi')
|
|
10
10
|
const EthereumABI = require('../protocols/ethereum/abi')
|
|
11
11
|
const { fixParameters } = require('../command-helpers/gluegun')
|
|
12
|
+
const { updateNetworksFile } = require('../command-helpers/network')
|
|
12
13
|
|
|
13
14
|
const HELP = `
|
|
14
15
|
${chalk.bold('graph add')} <address> [<subgraph-manifest default: "./subgraph.yaml">]
|
|
@@ -18,11 +19,12 @@ ${chalk.dim('Options:')}
|
|
|
18
19
|
--abi <path> Path to the contract ABI (default: download from Etherscan)
|
|
19
20
|
--contract-name Name of the contract (default: Contract)
|
|
20
21
|
--merge-entities Whether to merge entities with the same name (default: false)
|
|
22
|
+
--network-file <path> Networks config file path (default: "./networks.json")
|
|
21
23
|
-h, --help Show usage information
|
|
22
24
|
`
|
|
23
25
|
|
|
24
26
|
module.exports = {
|
|
25
|
-
description: '
|
|
27
|
+
description: 'Adds a new datasource to a subgraph',
|
|
26
28
|
run: async toolbox => {
|
|
27
29
|
// Obtain tools
|
|
28
30
|
let { print, system } = toolbox
|
|
@@ -33,20 +35,12 @@ module.exports = {
|
|
|
33
35
|
contractName,
|
|
34
36
|
h,
|
|
35
37
|
help,
|
|
36
|
-
mergeEntities
|
|
38
|
+
mergeEntities,
|
|
39
|
+
networkFile
|
|
37
40
|
} = toolbox.parameters.options
|
|
38
41
|
|
|
39
|
-
let address = toolbox.parameters.first
|
|
40
|
-
let manifestPath = toolbox.parameters.second || './subgraph.yaml'
|
|
41
42
|
contractName = contractName || 'Contract'
|
|
42
43
|
|
|
43
|
-
// Validate the address
|
|
44
|
-
if (!address) {
|
|
45
|
-
print.error('No contract address provided')
|
|
46
|
-
process.exitCode = 1
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
50
44
|
try {
|
|
51
45
|
fixParameters(toolbox.parameters, {
|
|
52
46
|
h,
|
|
@@ -59,12 +53,22 @@ module.exports = {
|
|
|
59
53
|
return
|
|
60
54
|
}
|
|
61
55
|
|
|
56
|
+
let address = toolbox.parameters.first || toolbox.parameters.array[0]
|
|
57
|
+
let manifestPath = toolbox.parameters.second || toolbox.parameters.array[1] || './subgraph.yaml'
|
|
58
|
+
|
|
62
59
|
// Show help text if requested
|
|
63
60
|
if (help || h) {
|
|
64
61
|
print.info(HELP)
|
|
65
62
|
return
|
|
66
63
|
}
|
|
67
64
|
|
|
65
|
+
// Validate the address
|
|
66
|
+
if (!address) {
|
|
67
|
+
print.error('No contract address provided')
|
|
68
|
+
process.exitCode = 1
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
68
72
|
const dataSourcesAndTemplates = await DataSourcesExtractor.fromFilePath(manifestPath)
|
|
69
73
|
let protocol = Protocol.fromDataSources(dataSourcesAndTemplates)
|
|
70
74
|
let manifest = await Subgraph.load(manifestPath, { protocol })
|
|
@@ -99,8 +103,12 @@ module.exports = {
|
|
|
99
103
|
await writeSchema(ethabi, protocol, result.getIn(['schema', 'file']), collisionEntities)
|
|
100
104
|
await writeMapping(ethabi, protocol, contractName, collisionEntities)
|
|
101
105
|
|
|
106
|
+
if (protocol.hasEvents()) {
|
|
107
|
+
await writeTestsFiles(ethabi, contractName)
|
|
108
|
+
}
|
|
109
|
+
|
|
102
110
|
let dataSources = result.get('dataSources')
|
|
103
|
-
let dataSource = await generateDataSource(protocol,
|
|
111
|
+
let dataSource = await generateDataSource(protocol,
|
|
104
112
|
contractName, network, address, ethabi)
|
|
105
113
|
|
|
106
114
|
// Handle the collisions edge case by copying another data source yaml data
|
|
@@ -120,6 +128,10 @@ module.exports = {
|
|
|
120
128
|
|
|
121
129
|
await Subgraph.write(result, manifestPath)
|
|
122
130
|
|
|
131
|
+
// Update networks.json
|
|
132
|
+
const networksFile = networkFile || "./networks.json"
|
|
133
|
+
await updateNetworksFile(toolbox, network, contractName, address, networksFile)
|
|
134
|
+
|
|
123
135
|
// Detect Yarn and/or NPM
|
|
124
136
|
let yarn = await system.which('yarn')
|
|
125
137
|
let npm = await system.which('npm')
|
|
@@ -173,12 +185,12 @@ const updateEventNamesOnCollision = (ethabi, entities, contractName, mergeEntiti
|
|
|
173
185
|
if (dataRow.get('type') === 'event'){
|
|
174
186
|
if (entities.indexOf(dataRow.get('name')) !== -1) {
|
|
175
187
|
if (entities.indexOf(`${contractName}${dataRow.get('name')}`) !== -1) {
|
|
176
|
-
print.error(`Contract name ('${contractName}')
|
|
188
|
+
print.error(`Contract name ('${contractName}')
|
|
177
189
|
+ event name ('${dataRow.get('name')}') entity already exists.`)
|
|
178
190
|
process.exitCode = 1
|
|
179
191
|
return
|
|
180
192
|
}
|
|
181
|
-
|
|
193
|
+
|
|
182
194
|
if (mergeEntities) {
|
|
183
195
|
collisionEntities.push(dataRow.get('name'))
|
|
184
196
|
abiData = abiData.asImmutable().delete(i) // needs to be immutable when deleting, yes you read that right - https://github.com/immutable-js/immutable-js/issues/1901
|
package/src/commands/build.js
CHANGED
|
@@ -17,8 +17,8 @@ Options:
|
|
|
17
17
|
-t, --output-format <format> Output format for mappings (wasm, wast) (default: wasm)
|
|
18
18
|
--skip-migrations Skip subgraph migrations (default: false)
|
|
19
19
|
-w, --watch Regenerate types when subgraph files change (default: false)
|
|
20
|
-
--network <name> Network to use from networks
|
|
21
|
-
--network-file <path>
|
|
20
|
+
--network <name> Network configuration to use from the networks config file
|
|
21
|
+
--network-file <path> Networks config file path (default: "./networks.json")
|
|
22
22
|
`
|
|
23
23
|
|
|
24
24
|
module.exports = {
|
package/src/commands/deploy.js
CHANGED
|
@@ -14,6 +14,7 @@ const { assertManifestApiVersion, assertGraphTsVersion } = require('../command-h
|
|
|
14
14
|
const DataSourcesExtractor = require('../command-helpers/data-sources')
|
|
15
15
|
const { validateStudioNetwork } = require('../command-helpers/studio')
|
|
16
16
|
const Protocol = require('../protocols')
|
|
17
|
+
const { updateSubgraphNetwork } = require('../command-helpers/network')
|
|
17
18
|
|
|
18
19
|
const HELP = `
|
|
19
20
|
${chalk.bold('graph deploy')} [options] ${chalk.bold('<subgraph-name>')} ${chalk.bold(
|
|
@@ -22,18 +23,21 @@ ${chalk.bold('graph deploy')} [options] ${chalk.bold('<subgraph-name>')} ${chalk
|
|
|
22
23
|
|
|
23
24
|
Options:
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
--product <subgraph-studio|hosted-service>
|
|
26
27
|
Selects the product to which to deploy
|
|
27
|
-
|
|
28
|
-
-g,
|
|
29
|
-
|
|
30
|
-
-l
|
|
31
|
-
-h,
|
|
32
|
-
-i,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
--studio Shortcut for --product subgraph-studio
|
|
29
|
+
-g, --node <node> Graph node to which to deploy
|
|
30
|
+
--deploy-key <key> User deploy key
|
|
31
|
+
-l --version-label <label> Version label used for the deployment
|
|
32
|
+
-h, --help Show usage information
|
|
33
|
+
-i, --ipfs <node> Upload build results to an IPFS node (default: ${DEFAULT_IPFS_URL})
|
|
34
|
+
-hdr, --headers <map> Add custom headers that will be used by the IPFS HTTP client (default: {})
|
|
35
|
+
--debug-fork ID of a remote subgraph whose store will be GraphQL queried
|
|
36
|
+
-o, --output-dir <path> Output directory for build results (default: build/)
|
|
37
|
+
--skip-migrations Skip subgraph migrations (default: false)
|
|
38
|
+
-w, --watch Regenerate types when subgraph files change (default: false)
|
|
39
|
+
--network <name> Network configuration to use from the networks config file
|
|
40
|
+
--network-file <path> Networks config file path (default: "./networks.json")
|
|
37
41
|
`
|
|
38
42
|
|
|
39
43
|
const processForm = async (
|
|
@@ -51,7 +55,7 @@ const processForm = async (
|
|
|
51
55
|
name: 'product',
|
|
52
56
|
message: 'Product for which to deploy',
|
|
53
57
|
choices: ['subgraph-studio', 'hosted-service'],
|
|
54
|
-
skip:
|
|
58
|
+
skip:
|
|
55
59
|
product === 'subgraph-studio' ||
|
|
56
60
|
product === 'hosted-service' ||
|
|
57
61
|
studio !== undefined || node !== undefined,
|
|
@@ -91,6 +95,8 @@ module.exports = {
|
|
|
91
95
|
i,
|
|
92
96
|
help,
|
|
93
97
|
ipfs,
|
|
98
|
+
headers,
|
|
99
|
+
hdr,
|
|
94
100
|
node,
|
|
95
101
|
o,
|
|
96
102
|
outputDir,
|
|
@@ -98,16 +104,27 @@ module.exports = {
|
|
|
98
104
|
w,
|
|
99
105
|
watch,
|
|
100
106
|
debugFork,
|
|
107
|
+
network,
|
|
108
|
+
networkFile,
|
|
101
109
|
} = toolbox.parameters.options
|
|
102
110
|
|
|
103
111
|
// Support both long and short option variants
|
|
104
112
|
help = help || h
|
|
105
113
|
ipfs = ipfs || i || DEFAULT_IPFS_URL
|
|
114
|
+
headers = headers || hdr || "{}"
|
|
106
115
|
node = node || g
|
|
107
116
|
outputDir = outputDir || o
|
|
108
117
|
watch = watch || w
|
|
109
118
|
versionLabel = versionLabel || l
|
|
110
119
|
|
|
120
|
+
try {
|
|
121
|
+
headers = JSON.parse(headers)
|
|
122
|
+
} catch (e) {
|
|
123
|
+
print.error("Please make sure headers is a valid JSON value")
|
|
124
|
+
process.exitCode = 1
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
let subgraphName, manifest
|
|
112
129
|
try {
|
|
113
130
|
;[subgraphName, manifest] = fixParameters(toolbox.parameters, {
|
|
@@ -129,6 +146,10 @@ module.exports = {
|
|
|
129
146
|
manifest !== undefined && manifest !== ''
|
|
130
147
|
? manifest
|
|
131
148
|
: filesystem.resolve('subgraph.yaml')
|
|
149
|
+
networkFile =
|
|
150
|
+
networkFile !== undefined && networkFile !== ''
|
|
151
|
+
? networkFile
|
|
152
|
+
: filesystem.resolve("networks.json")
|
|
132
153
|
|
|
133
154
|
try {
|
|
134
155
|
const dataSourcesAndTemplates = await DataSourcesExtractor.fromFilePath(manifest)
|
|
@@ -211,11 +232,17 @@ module.exports = {
|
|
|
211
232
|
return
|
|
212
233
|
}
|
|
213
234
|
|
|
235
|
+
if (network) {
|
|
236
|
+
let identifierName = protocol.getContract().identifierName()
|
|
237
|
+
await updateSubgraphNetwork(toolbox, manifest, network, networkFile, identifierName)
|
|
238
|
+
}
|
|
239
|
+
|
|
214
240
|
const isStudio = node.match(/studio/)
|
|
215
241
|
const isHostedService = node.match(/thegraph.com/) && !isStudio
|
|
216
242
|
|
|
217
243
|
let compiler = createCompiler(manifest, {
|
|
218
244
|
ipfs,
|
|
245
|
+
headers,
|
|
219
246
|
outputDir,
|
|
220
247
|
outputFormat: 'wasm',
|
|
221
248
|
skipMigrations,
|
package/src/commands/init.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
const chalk = require('chalk')
|
|
2
|
-
const fetch = require('node-fetch')
|
|
3
|
-
const immutable = require('immutable')
|
|
4
2
|
const os = require('os')
|
|
5
3
|
const path = require('path')
|
|
6
4
|
const toolbox = require('gluegun/toolbox')
|
|
7
|
-
const
|
|
5
|
+
const fs = require('fs')
|
|
6
|
+
const graphCli = require('../cli')
|
|
8
7
|
|
|
9
8
|
const {
|
|
10
9
|
getSubgraphBasename,
|
|
@@ -423,9 +422,9 @@ module.exports = {
|
|
|
423
422
|
contractName,
|
|
424
423
|
node,
|
|
425
424
|
studio,
|
|
426
|
-
product
|
|
425
|
+
product
|
|
427
426
|
},
|
|
428
|
-
{ commands },
|
|
427
|
+
{ commands, addContract: false },
|
|
429
428
|
)
|
|
430
429
|
}
|
|
431
430
|
|
|
@@ -484,9 +483,9 @@ module.exports = {
|
|
|
484
483
|
contractName: inputs.contractName,
|
|
485
484
|
node,
|
|
486
485
|
studio: inputs.studio,
|
|
487
|
-
product: inputs.product
|
|
486
|
+
product: inputs.product
|
|
488
487
|
},
|
|
489
|
-
{ commands },
|
|
488
|
+
{ commands, addContract: true },
|
|
490
489
|
)
|
|
491
490
|
}
|
|
492
491
|
},
|
|
@@ -720,9 +719,9 @@ const initSubgraphFromContract = async (
|
|
|
720
719
|
contractName,
|
|
721
720
|
node,
|
|
722
721
|
studio,
|
|
723
|
-
product
|
|
722
|
+
product
|
|
724
723
|
},
|
|
725
|
-
{ commands },
|
|
724
|
+
{ commands, addContract },
|
|
726
725
|
) => {
|
|
727
726
|
let { print } = toolbox
|
|
728
727
|
|
|
@@ -815,5 +814,85 @@ const initSubgraphFromContract = async (
|
|
|
815
814
|
return
|
|
816
815
|
}
|
|
817
816
|
|
|
817
|
+
while (addContract) {
|
|
818
|
+
addContract = await addAnotherContract(toolbox, { protocolInstance, directory })
|
|
819
|
+
}
|
|
820
|
+
|
|
818
821
|
printNextSteps(toolbox, { subgraphName, directory }, { commands })
|
|
819
822
|
}
|
|
823
|
+
|
|
824
|
+
const addAnotherContract = async (toolbox, { protocolInstance, directory }) => {
|
|
825
|
+
const addContractConfirmation = await toolbox.prompt.confirm('Add another contract?')
|
|
826
|
+
|
|
827
|
+
if (addContractConfirmation) {
|
|
828
|
+
let abiFromFile
|
|
829
|
+
let ProtocolContract = protocolInstance.getContract()
|
|
830
|
+
|
|
831
|
+
let questions = [
|
|
832
|
+
{
|
|
833
|
+
type: 'input',
|
|
834
|
+
name: 'contract',
|
|
835
|
+
message: () => `Contract ${ProtocolContract.identifierName()}`,
|
|
836
|
+
validate: async (value) => {
|
|
837
|
+
// Validate whether the contract is valid
|
|
838
|
+
const { valid, error } = validateContract(value, ProtocolContract)
|
|
839
|
+
return valid ? true : error
|
|
840
|
+
},
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
type: 'select',
|
|
844
|
+
name: 'localAbi',
|
|
845
|
+
message: 'Provide local ABI path?',
|
|
846
|
+
choices: ['yes', 'no'],
|
|
847
|
+
result: (value) => {
|
|
848
|
+
abiFromFile = value === 'yes' ? true : false
|
|
849
|
+
return abiFromFile
|
|
850
|
+
},
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
type: 'input',
|
|
854
|
+
name: 'abi',
|
|
855
|
+
message: 'ABI file (path)',
|
|
856
|
+
skip: () => abiFromFile === false
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
type: 'input',
|
|
860
|
+
name: 'contractName',
|
|
861
|
+
message: 'Contract Name',
|
|
862
|
+
initial: 'Contract',
|
|
863
|
+
validate: (value) => value && value.length > 0,
|
|
864
|
+
},
|
|
865
|
+
]
|
|
866
|
+
|
|
867
|
+
// Get the cwd before process.chdir in order to switch back in the end of command execution
|
|
868
|
+
const cwd = process.cwd();
|
|
869
|
+
|
|
870
|
+
try {
|
|
871
|
+
let { abi, contract, contractName } = await toolbox.prompt.ask(questions)
|
|
872
|
+
|
|
873
|
+
if (fs.existsSync(directory)) {
|
|
874
|
+
process.chdir(directory)
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
let commandLine = ['add', contract, '--contract-name', contractName]
|
|
878
|
+
|
|
879
|
+
if (abiFromFile) {
|
|
880
|
+
if (abi.includes(directory)) {
|
|
881
|
+
commandLine.push('--abi', path.normalize(abi.replace(directory, '')))
|
|
882
|
+
} else {
|
|
883
|
+
commandLine.push('--abi', abi)
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
await graphCli.run(commandLine)
|
|
888
|
+
} catch (e) {
|
|
889
|
+
toolbox.print.error(e)
|
|
890
|
+
process.exit(1)
|
|
891
|
+
}
|
|
892
|
+
finally {
|
|
893
|
+
process.chdir(cwd)
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
return addContractConfirmation
|
|
898
|
+
}
|
package/src/commands/test.js
CHANGED
|
@@ -180,10 +180,8 @@ async function getPlatform(logsOpt) {
|
|
|
180
180
|
|
|
181
181
|
if (arch === 'x64' || isM1) {
|
|
182
182
|
if (type === 'Darwin') {
|
|
183
|
-
if (majorVersion === 19) {
|
|
184
|
-
return 'binary-macos-10.15'
|
|
185
|
-
} else if (majorVersion === 18) {
|
|
186
|
-
return 'binary-macos-10.14'
|
|
183
|
+
if (majorVersion === 18 || majorVersion === 19) {
|
|
184
|
+
return 'binary-macos-10.15' // GitHub dropped support for macOS 10.14 in Actions, but it seems 10.15 binary works on 10.14 too
|
|
187
185
|
} else if (isM1) {
|
|
188
186
|
return 'binary-macos-11-m1'
|
|
189
187
|
}
|
|
@@ -196,8 +194,6 @@ async function getPlatform(logsOpt) {
|
|
|
196
194
|
} else {
|
|
197
195
|
return 'binary-linux-20'
|
|
198
196
|
}
|
|
199
|
-
} else if (type === 'Windows_NT') {
|
|
200
|
-
return 'binary-windows'
|
|
201
197
|
}
|
|
202
198
|
}
|
|
203
199
|
|
|
@@ -19,7 +19,7 @@ module.exports = {
|
|
|
19
19
|
return 'graph-ts dependency not installed yet'
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let manifest = loadManifest(manifestFile)
|
|
22
|
+
let manifest = await loadManifest(manifestFile)
|
|
23
23
|
return (
|
|
24
24
|
// Only migrate if the graph-ts version is >= 0.22.0...
|
|
25
25
|
// Coerce needed because we may be dealing with an alpha version
|
|
@@ -19,7 +19,7 @@ module.exports = {
|
|
|
19
19
|
return 'graph-ts dependency not installed yet'
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let manifest = loadManifest(manifestFile)
|
|
22
|
+
let manifest = await loadManifest(manifestFile)
|
|
23
23
|
return (
|
|
24
24
|
// Only migrate if the graph-ts version is >= 0.24.0...
|
|
25
25
|
// Coerce needed because we may be dealing with an alpha version
|
package/src/protocols/index.js
CHANGED
|
@@ -52,6 +52,7 @@ module.exports = class Protocol {
|
|
|
52
52
|
'matic',
|
|
53
53
|
'mumbai',
|
|
54
54
|
'fantom',
|
|
55
|
+
'fantom-testnet',
|
|
55
56
|
'bsc',
|
|
56
57
|
'chapel',
|
|
57
58
|
'clover',
|
|
@@ -60,6 +61,8 @@ module.exports = class Protocol {
|
|
|
60
61
|
'celo',
|
|
61
62
|
'celo-alfajores',
|
|
62
63
|
'fuse',
|
|
64
|
+
'moonbeam',
|
|
65
|
+
'moonriver',
|
|
63
66
|
'mbase',
|
|
64
67
|
'arbitrum-one',
|
|
65
68
|
'arbitrum-rinkeby',
|
|
@@ -243,6 +243,265 @@ export function handleExampleEvent1(event: ExampleEvent1Event): void {
|
|
|
243
243
|
entity.a = event.params.a
|
|
244
244
|
entity.save()
|
|
245
245
|
}
|
|
246
|
+
`)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
test('Test Files (default)', () => {
|
|
250
|
+
const files = scaffoldWithIndexEvents.generateTests()
|
|
251
|
+
const testFile = files['contract.test.ts']
|
|
252
|
+
const utilsFile = files['contract-utils.ts']
|
|
253
|
+
expect(testFile).toEqual(`\
|
|
254
|
+
import {
|
|
255
|
+
assert,
|
|
256
|
+
describe,
|
|
257
|
+
test,
|
|
258
|
+
clearStore,
|
|
259
|
+
beforeAll,
|
|
260
|
+
afterAll
|
|
261
|
+
} from \"matchstick-as/assembly/index\"
|
|
262
|
+
import { BigInt, Bytes } from \"@graphprotocol/graph-ts\"
|
|
263
|
+
import { ExampleEvent } from \"../generated/schema\"
|
|
264
|
+
import { ExampleEvent as ExampleEventEvent } from \"../generated/Contract/Contract\"
|
|
265
|
+
import { handleExampleEvent } from \"../src/contract\"
|
|
266
|
+
import { createExampleEventEvent } from \"./contract-utils\"
|
|
267
|
+
|
|
268
|
+
// Tests structure (matchstick-as >=0.5.0)
|
|
269
|
+
// https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0
|
|
270
|
+
|
|
271
|
+
describe(\"Describe entity assertions\", () => {
|
|
272
|
+
beforeAll(() => {
|
|
273
|
+
let a = BigInt.fromI32(234)
|
|
274
|
+
let b = [Bytes.fromI32(1234567890)]
|
|
275
|
+
let param2 = \"Example string value\"
|
|
276
|
+
let c = \"ethereum.Tuple Not implemented\"
|
|
277
|
+
let d = \"Example string value\"
|
|
278
|
+
let newExampleEventEvent = createExampleEventEvent(a, b, param2, c, d)
|
|
279
|
+
handleExampleEvent(newExampleEventEvent)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
afterAll(() => {
|
|
283
|
+
clearStore()
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
// For more test scenarios, see:
|
|
287
|
+
// https://thegraph.com/docs/en/developer/matchstick/#write-a-unit-test
|
|
288
|
+
|
|
289
|
+
test(\"ExampleEvent created and stored\", () => {
|
|
290
|
+
assert.entityCount(\"ExampleEvent\", 1)
|
|
291
|
+
|
|
292
|
+
// 0xa16081f360e3847006db660bae1c6d1b2e17ec2a is the default address used in newMockEvent() function
|
|
293
|
+
assert.fieldEquals(
|
|
294
|
+
\"ExampleEvent\",
|
|
295
|
+
\"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1\",
|
|
296
|
+
\"a\",
|
|
297
|
+
\"234\"
|
|
298
|
+
)
|
|
299
|
+
assert.fieldEquals(
|
|
300
|
+
\"ExampleEvent\",
|
|
301
|
+
\"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1\",
|
|
302
|
+
\"b\",
|
|
303
|
+
\"[1234567890]\"
|
|
304
|
+
)
|
|
305
|
+
assert.fieldEquals(
|
|
306
|
+
\"ExampleEvent\",
|
|
307
|
+
\"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1\",
|
|
308
|
+
\"param2\",
|
|
309
|
+
\"Example string value\"
|
|
310
|
+
)
|
|
311
|
+
assert.fieldEquals(
|
|
312
|
+
\"ExampleEvent\",
|
|
313
|
+
\"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1\",
|
|
314
|
+
\"c\",
|
|
315
|
+
\"ethereum.Tuple Not implemented\"
|
|
316
|
+
)
|
|
317
|
+
assert.fieldEquals(
|
|
318
|
+
\"ExampleEvent\",
|
|
319
|
+
\"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1\",
|
|
320
|
+
\"d\",
|
|
321
|
+
\"Example string value\"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
// More assert options:
|
|
325
|
+
// https://thegraph.com/docs/en/developer/matchstick/#asserts
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
`)
|
|
329
|
+
expect(utilsFile).toEqual(`\
|
|
330
|
+
import { newMockEvent } from \"matchstick-as\"
|
|
331
|
+
import { ethereum, BigInt, Bytes } from \"@graphprotocol/graph-ts\"
|
|
332
|
+
import { ExampleEvent, ExampleEvent1 } from \"../generated/Contract/Contract\"
|
|
333
|
+
|
|
334
|
+
export function createExampleEventEvent(
|
|
335
|
+
a: BigInt,
|
|
336
|
+
b: Array<Bytes>,
|
|
337
|
+
param2: string,
|
|
338
|
+
c: ethereum.Tuple,
|
|
339
|
+
d: string
|
|
340
|
+
): ExampleEvent {
|
|
341
|
+
let exampleEventEvent = changetype<ExampleEvent>(newMockEvent())
|
|
342
|
+
|
|
343
|
+
exampleEventEvent.parameters = new Array()
|
|
344
|
+
|
|
345
|
+
exampleEventEvent.parameters.push(
|
|
346
|
+
new ethereum.EventParam(\"a\", ethereum.Value.fromUnsignedBigInt(a))
|
|
347
|
+
)
|
|
348
|
+
exampleEventEvent.parameters.push(
|
|
349
|
+
new ethereum.EventParam(\"b\", ethereum.Value.fromBytesArray(b))
|
|
350
|
+
)
|
|
351
|
+
exampleEventEvent.parameters.push(
|
|
352
|
+
new ethereum.EventParam(\"param2\", ethereum.Value.fromString(param2))
|
|
353
|
+
)
|
|
354
|
+
exampleEventEvent.parameters.push(
|
|
355
|
+
new ethereum.EventParam(\"c\", ethereum.Value.fromTuple(c))
|
|
356
|
+
)
|
|
357
|
+
exampleEventEvent.parameters.push(
|
|
358
|
+
new ethereum.EventParam(\"d\", ethereum.Value.fromString(d))
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
return exampleEventEvent
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function createExampleEvent1Event(a: Bytes): ExampleEvent1 {
|
|
365
|
+
let exampleEvent1Event = changetype<ExampleEvent1>(newMockEvent())
|
|
366
|
+
|
|
367
|
+
exampleEvent1Event.parameters = new Array()
|
|
368
|
+
|
|
369
|
+
exampleEvent1Event.parameters.push(
|
|
370
|
+
new ethereum.EventParam(\"a\", ethereum.Value.fromFixedBytes(a))
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
return exampleEvent1Event
|
|
374
|
+
}
|
|
375
|
+
`)
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
test('Test Files (for indexing events)', () => {
|
|
379
|
+
const files = scaffoldWithIndexEvents.generateTests()
|
|
380
|
+
const testFile = files['contract.test.ts']
|
|
381
|
+
const utilsFile = files['contract-utils.ts']
|
|
382
|
+
|
|
383
|
+
expect(testFile).toEqual(`\
|
|
384
|
+
import {
|
|
385
|
+
assert,
|
|
386
|
+
describe,
|
|
387
|
+
test,
|
|
388
|
+
clearStore,
|
|
389
|
+
beforeAll,
|
|
390
|
+
afterAll
|
|
391
|
+
} from \"matchstick-as/assembly/index\"
|
|
392
|
+
import { BigInt, Bytes } from \"@graphprotocol/graph-ts\"
|
|
393
|
+
import { ExampleEvent } from \"../generated/schema\"
|
|
394
|
+
import { ExampleEvent as ExampleEventEvent } from \"../generated/Contract/Contract\"
|
|
395
|
+
import { handleExampleEvent } from \"../src/contract\"
|
|
396
|
+
import { createExampleEventEvent } from \"./contract-utils\"
|
|
397
|
+
|
|
398
|
+
// Tests structure (matchstick-as >=0.5.0)
|
|
399
|
+
// https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0
|
|
400
|
+
|
|
401
|
+
describe(\"Describe entity assertions\", () => {
|
|
402
|
+
beforeAll(() => {
|
|
403
|
+
let a = BigInt.fromI32(234)
|
|
404
|
+
let b = [Bytes.fromI32(1234567890)]
|
|
405
|
+
let param2 = \"Example string value\"
|
|
406
|
+
let c = \"ethereum.Tuple Not implemented\"
|
|
407
|
+
let d = \"Example string value\"
|
|
408
|
+
let newExampleEventEvent = createExampleEventEvent(a, b, param2, c, d)
|
|
409
|
+
handleExampleEvent(newExampleEventEvent)
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
afterAll(() => {
|
|
413
|
+
clearStore()
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
// For more test scenarios, see:
|
|
417
|
+
// https://thegraph.com/docs/en/developer/matchstick/#write-a-unit-test
|
|
418
|
+
|
|
419
|
+
test(\"ExampleEvent created and stored\", () => {
|
|
420
|
+
assert.entityCount(\"ExampleEvent\", 1)
|
|
421
|
+
|
|
422
|
+
// 0xa16081f360e3847006db660bae1c6d1b2e17ec2a is the default address used in newMockEvent() function
|
|
423
|
+
assert.fieldEquals(
|
|
424
|
+
\"ExampleEvent\",
|
|
425
|
+
\"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1\",
|
|
426
|
+
\"a\",
|
|
427
|
+
\"234\"
|
|
428
|
+
)
|
|
429
|
+
assert.fieldEquals(
|
|
430
|
+
\"ExampleEvent\",
|
|
431
|
+
\"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1\",
|
|
432
|
+
\"b\",
|
|
433
|
+
\"[1234567890]\"
|
|
434
|
+
)
|
|
435
|
+
assert.fieldEquals(
|
|
436
|
+
\"ExampleEvent\",
|
|
437
|
+
\"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1\",
|
|
438
|
+
\"param2\",
|
|
439
|
+
\"Example string value\"
|
|
440
|
+
)
|
|
441
|
+
assert.fieldEquals(
|
|
442
|
+
\"ExampleEvent\",
|
|
443
|
+
\"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1\",
|
|
444
|
+
\"c\",
|
|
445
|
+
\"ethereum.Tuple Not implemented\"
|
|
446
|
+
)
|
|
447
|
+
assert.fieldEquals(
|
|
448
|
+
\"ExampleEvent\",
|
|
449
|
+
\"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1\",
|
|
450
|
+
\"d\",
|
|
451
|
+
\"Example string value\"
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
// More assert options:
|
|
455
|
+
// https://thegraph.com/docs/en/developer/matchstick/#asserts
|
|
456
|
+
})
|
|
457
|
+
})
|
|
458
|
+
`)
|
|
459
|
+
expect(utilsFile).toEqual(`\
|
|
460
|
+
import { newMockEvent } from \"matchstick-as\"
|
|
461
|
+
import { ethereum, BigInt, Bytes } from \"@graphprotocol/graph-ts\"
|
|
462
|
+
import { ExampleEvent, ExampleEvent1 } from \"../generated/Contract/Contract\"
|
|
463
|
+
|
|
464
|
+
export function createExampleEventEvent(
|
|
465
|
+
a: BigInt,
|
|
466
|
+
b: Array<Bytes>,
|
|
467
|
+
param2: string,
|
|
468
|
+
c: ethereum.Tuple,
|
|
469
|
+
d: string
|
|
470
|
+
): ExampleEvent {
|
|
471
|
+
let exampleEventEvent = changetype<ExampleEvent>(newMockEvent())
|
|
472
|
+
|
|
473
|
+
exampleEventEvent.parameters = new Array()
|
|
474
|
+
|
|
475
|
+
exampleEventEvent.parameters.push(
|
|
476
|
+
new ethereum.EventParam(\"a\", ethereum.Value.fromUnsignedBigInt(a))
|
|
477
|
+
)
|
|
478
|
+
exampleEventEvent.parameters.push(
|
|
479
|
+
new ethereum.EventParam(\"b\", ethereum.Value.fromBytesArray(b))
|
|
480
|
+
)
|
|
481
|
+
exampleEventEvent.parameters.push(
|
|
482
|
+
new ethereum.EventParam(\"param2\", ethereum.Value.fromString(param2))
|
|
483
|
+
)
|
|
484
|
+
exampleEventEvent.parameters.push(
|
|
485
|
+
new ethereum.EventParam(\"c\", ethereum.Value.fromTuple(c))
|
|
486
|
+
)
|
|
487
|
+
exampleEventEvent.parameters.push(
|
|
488
|
+
new ethereum.EventParam(\"d\", ethereum.Value.fromString(d))
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
return exampleEventEvent
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function createExampleEvent1Event(a: Bytes): ExampleEvent1 {
|
|
495
|
+
let exampleEvent1Event = changetype<ExampleEvent1>(newMockEvent())
|
|
496
|
+
|
|
497
|
+
exampleEvent1Event.parameters = new Array()
|
|
498
|
+
|
|
499
|
+
exampleEvent1Event.parameters.push(
|
|
500
|
+
new ethereum.EventParam(\"a\", ethereum.Value.fromFixedBytes(a))
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
return exampleEvent1Event
|
|
504
|
+
}
|
|
246
505
|
`)
|
|
247
506
|
})
|
|
248
507
|
})
|
package/src/scaffold/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
generateExampleEntityType,
|
|
16
16
|
} = require('./schema')
|
|
17
17
|
const { generateEventIndexingHandlers } = require('./mapping')
|
|
18
|
+
const { generateTestsFiles } = require('./tests')
|
|
18
19
|
const { getSubgraphBasename } = require('../command-helpers/subgraph')
|
|
19
20
|
|
|
20
21
|
module.exports = class Scaffold {
|
|
@@ -48,11 +49,15 @@ module.exports = class Scaffold {
|
|
|
48
49
|
`--node http://localhost:8020/ ` +
|
|
49
50
|
`--ipfs http://localhost:5001 ` +
|
|
50
51
|
this.subgraphName,
|
|
52
|
+
'test': 'graph test',
|
|
51
53
|
},
|
|
52
54
|
dependencies: {
|
|
53
55
|
'@graphprotocol/graph-cli': GRAPH_CLI_VERSION,
|
|
54
56
|
'@graphprotocol/graph-ts': `0.27.0`,
|
|
55
57
|
},
|
|
58
|
+
devDependencies: {
|
|
59
|
+
'matchstick-as': `0.5.0`,
|
|
60
|
+
},
|
|
56
61
|
}),
|
|
57
62
|
{ parser: 'json' },
|
|
58
63
|
)
|
|
@@ -137,6 +142,12 @@ dataSources:
|
|
|
137
142
|
: undefined
|
|
138
143
|
}
|
|
139
144
|
|
|
145
|
+
generateTests() {
|
|
146
|
+
return this.protocol.hasEvents()
|
|
147
|
+
? generateTestsFiles(this.contractName, abiEvents(this.abi).toJS(), this.indexEvents)
|
|
148
|
+
: undefined
|
|
149
|
+
}
|
|
150
|
+
|
|
140
151
|
generate() {
|
|
141
152
|
return {
|
|
142
153
|
'package.json': this.generatePackageJson(),
|
|
@@ -145,6 +156,7 @@ dataSources:
|
|
|
145
156
|
'tsconfig.json': this.generateTsConfig(),
|
|
146
157
|
src: { [`${strings.kebabCase(this.contractName)}.ts`]: this.generateMapping() },
|
|
147
158
|
abis: this.generateABIs(),
|
|
159
|
+
tests: this.generateTests(),
|
|
148
160
|
}
|
|
149
161
|
}
|
|
150
162
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const prettier = require('prettier')
|
|
2
|
+
const { strings } = require('gluegun')
|
|
3
|
+
const { ascTypeForEthereum, ethereumFromAsc } = require("../codegen/types")
|
|
4
|
+
|
|
5
|
+
const VARIABLES_VALUES = {
|
|
6
|
+
"i32": 123,
|
|
7
|
+
"BigInt": 234,
|
|
8
|
+
"Bytes": 1234567890,
|
|
9
|
+
"Address": "0x0000000000000000000000000000000000000001",
|
|
10
|
+
"string": "Example string value",
|
|
11
|
+
"bool": true,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const generateTestsFiles = (contract, events, indexEvents) => {
|
|
15
|
+
const eventsTypes = events
|
|
16
|
+
.flatMap(event =>
|
|
17
|
+
event
|
|
18
|
+
.inputs
|
|
19
|
+
.map(input => {
|
|
20
|
+
// If the asc type is Array<T> we need to check if T is a native type or a custom graph-ts type
|
|
21
|
+
// If we don't do that we may miss a type that should be imported from graph-ts
|
|
22
|
+
const ascType = ascTypeForEthereum(input.type)
|
|
23
|
+
const inner = fetchArrayInnerType(ascType)
|
|
24
|
+
return inner ? inner[1] : ascType
|
|
25
|
+
})
|
|
26
|
+
).filter(type => !type.startsWith("ethereum.") && !isNativeType(type))
|
|
27
|
+
const importTypes = [...new Set(eventsTypes)].join(', ')
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
[`${strings.kebabCase(contract)}.test.ts`]: prettier.format(generateExampleTest(contract, events[0], indexEvents, importTypes), { parser: 'typescript', semi: false }),
|
|
31
|
+
[`${strings.kebabCase(contract)}-utils.ts`]: prettier.format(generateTestHelper(contract, events, importTypes), { parser: 'typescript', semi: false }),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
Generates the arguments that will be passed to the mock event function from the event inputs. Example:
|
|
37
|
+
let id = BigInt.fromI32(234)
|
|
38
|
+
let owner = Address.fromString("0x0000000000000000000000000000000000000001")
|
|
39
|
+
let displayName = "Example string value"
|
|
40
|
+
let imageUrl = "Example string value"
|
|
41
|
+
*/
|
|
42
|
+
const generateArguments = (eventInputs) => {
|
|
43
|
+
return eventInputs.map((input, index) => {
|
|
44
|
+
let ascType = ascTypeForEthereum(input.type)
|
|
45
|
+
return `let ${input.name || `param${index}`} = ${assignValue(ascType, input.name)}`
|
|
46
|
+
}).join('\n')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Generates the value that will be assigned to a variable in generateArguments()
|
|
50
|
+
const assignValue = (type) => {
|
|
51
|
+
switch (type) {
|
|
52
|
+
case "string":
|
|
53
|
+
return `"${VARIABLES_VALUES[type]}"`
|
|
54
|
+
case "BigInt":
|
|
55
|
+
return `BigInt.fromI32(${VARIABLES_VALUES[type]})`
|
|
56
|
+
case "Address":
|
|
57
|
+
return `Address.fromString("${VARIABLES_VALUES[type]}")`
|
|
58
|
+
case "Bytes":
|
|
59
|
+
return `Bytes.fromI32(${VARIABLES_VALUES[type]})`
|
|
60
|
+
case fetchArrayInnerType(type)?.input:
|
|
61
|
+
innerType = fetchArrayInnerType(type)[1]
|
|
62
|
+
return `[${assignValue(innerType)}]`
|
|
63
|
+
default:
|
|
64
|
+
let value = VARIABLES_VALUES[type]
|
|
65
|
+
return value ? value : `"${type} Not implemented"`
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
Generates the assert.fieldEquals() for a given entity and event inputs. Example:
|
|
71
|
+
assert.fieldEquals(
|
|
72
|
+
"ExampleEntity",
|
|
73
|
+
"0xa16081f360e3847006db660bae1c6d1b2e17ec2a",
|
|
74
|
+
"owner",
|
|
75
|
+
"0x0000000000000000000000000000000000000001"
|
|
76
|
+
)
|
|
77
|
+
*/
|
|
78
|
+
const generateFieldsAssertions = (entity, eventInputs, indexEvents) => eventInputs.filter(input => input.name != "id").map((input, index) =>
|
|
79
|
+
`assert.fieldEquals(
|
|
80
|
+
"${entity}",
|
|
81
|
+
"0xa16081f360e3847006db660bae1c6d1b2e17ec2a${indexEvents ? "-1" : ""}",
|
|
82
|
+
"${input.name || `param${index}`}",
|
|
83
|
+
"${expectedValue(ascTypeForEthereum(input.type))}"
|
|
84
|
+
)`
|
|
85
|
+
).join('\n')
|
|
86
|
+
|
|
87
|
+
// Returns the expected value for a given type in generateFieldsAssertions()
|
|
88
|
+
const expectedValue = type => {
|
|
89
|
+
switch (type) {
|
|
90
|
+
case fetchArrayInnerType(type)?.input:
|
|
91
|
+
innerType = fetchArrayInnerType(type)[1]
|
|
92
|
+
return `[${expectedValue(innerType)}]`
|
|
93
|
+
default:
|
|
94
|
+
let value = VARIABLES_VALUES[type]
|
|
95
|
+
return value ? value : `${type} Not implemented`
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Checks if the type is a native AS type or should be imported from graph-ts
|
|
100
|
+
const isNativeType = type => {
|
|
101
|
+
let natives = [
|
|
102
|
+
/i32/,
|
|
103
|
+
/string/,
|
|
104
|
+
/boolean/
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
return natives.some(rx => rx.test(type));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const fetchArrayInnerType = type => type.match(/Array<(.*?)>/)
|
|
111
|
+
|
|
112
|
+
// Generates the example test.ts file
|
|
113
|
+
const generateExampleTest = (contract, event, indexEvents, importTypes) => {
|
|
114
|
+
const entity = indexEvents ? `${event._alias}` : 'ExampleEntity'
|
|
115
|
+
const eventInputs = event.inputs
|
|
116
|
+
const eventName = event._alias
|
|
117
|
+
|
|
118
|
+
return `
|
|
119
|
+
import { assert, describe, test, clearStore, beforeAll, afterAll } from "matchstick-as/assembly/index"
|
|
120
|
+
import { ${importTypes} } from "@graphprotocol/graph-ts"
|
|
121
|
+
import { ${entity} } from "../generated/schema"
|
|
122
|
+
import { ${indexEvents ? `${eventName} as ${eventName}Event` : eventName} } from "../generated/${contract}/${contract}"
|
|
123
|
+
import { handle${eventName} } from "../src/${strings.kebabCase(contract)}"
|
|
124
|
+
import { create${eventName}Event } from "./${strings.kebabCase(contract)}-utils"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
// Tests structure (matchstick-as >=0.5.0)
|
|
128
|
+
// https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0
|
|
129
|
+
|
|
130
|
+
describe("Describe entity assertions", () => {
|
|
131
|
+
beforeAll(() => {
|
|
132
|
+
${generateArguments(eventInputs)}
|
|
133
|
+
let new${eventName}Event = create${eventName}Event(${eventInputs.map((input, index) => input.name || `param${index}`).join(', ')});
|
|
134
|
+
handle${eventName}(new${eventName}Event)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
afterAll(() => {
|
|
138
|
+
clearStore()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// For more test scenarios, see:
|
|
142
|
+
// https://thegraph.com/docs/en/developer/matchstick/#write-a-unit-test
|
|
143
|
+
|
|
144
|
+
test("${entity} created and stored", () => {
|
|
145
|
+
assert.entityCount('${entity}', 1)
|
|
146
|
+
|
|
147
|
+
// 0xa16081f360e3847006db660bae1c6d1b2e17ec2a is the default address used in newMockEvent() function
|
|
148
|
+
${generateFieldsAssertions(entity, eventInputs, indexEvents)}
|
|
149
|
+
|
|
150
|
+
// More assert options:
|
|
151
|
+
// https://thegraph.com/docs/en/developer/matchstick/#asserts
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
`
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Generates the utils helper file
|
|
158
|
+
const generateTestHelper = (contract, events, importTypes) => {
|
|
159
|
+
const eventsNames = events.map(event => event._alias)
|
|
160
|
+
|
|
161
|
+
return `
|
|
162
|
+
import { newMockEvent } from 'matchstick-as';
|
|
163
|
+
import { ethereum, ${importTypes} } from '@graphprotocol/graph-ts';
|
|
164
|
+
import { ${eventsNames.join(", ")} } from '../generated/${contract}/${contract}';
|
|
165
|
+
|
|
166
|
+
${generateMockedEvents(events).join("\n")}`
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const generateMockedEvents = events =>
|
|
170
|
+
events.reduce(
|
|
171
|
+
(acc, event) => acc.concat(generateMockedEvent(event)),
|
|
172
|
+
[],
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
const generateMockedEvent = event => {
|
|
176
|
+
const varName = `${strings.camelCase(event._alias)}Event`
|
|
177
|
+
const fnArgs = event.inputs.map((input, index) => `${input.name || `param${index}`}: ${ascTypeForEthereum(input.type)}`);
|
|
178
|
+
const ascToEth = event.inputs.map((input, index) => `${varName}.parameters.push(new ethereum.EventParam("${input.name || `param${index}`}", ${ethereumFromAsc(input.name || `param${index}`, input.type)}))`);
|
|
179
|
+
|
|
180
|
+
return `
|
|
181
|
+
export function create${event._alias}Event(${fnArgs.join(', ')}): ${event._alias} {
|
|
182
|
+
let ${varName} = changetype<${event._alias}>(newMockEvent());
|
|
183
|
+
|
|
184
|
+
${varName}.parameters = new Array();
|
|
185
|
+
|
|
186
|
+
${ascToEth.join('\n')}
|
|
187
|
+
|
|
188
|
+
return ${varName};
|
|
189
|
+
}
|
|
190
|
+
`
|
|
191
|
+
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = {
|
|
195
|
+
generateTestsFiles,
|
|
196
|
+
}
|
|
@@ -10,6 +10,14 @@ dataSources:
|
|
|
10
10
|
source:
|
|
11
11
|
account: wnear.flux-dev
|
|
12
12
|
startBlock: 1
|
|
13
|
+
accounts:
|
|
14
|
+
prefixes:
|
|
15
|
+
- some-prefix
|
|
16
|
+
- a-prefix.with-dot
|
|
17
|
+
suffixes:
|
|
18
|
+
- suffix.near
|
|
19
|
+
- near
|
|
20
|
+
- another-suffix.testnet
|
|
13
21
|
mapping:
|
|
14
22
|
apiVersion: 0.0.5
|
|
15
23
|
language: wasm/assemblyscript
|