@graphprotocol/graph-cli 0.31.0 → 0.33.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 +3 -2
- package/package.json +1 -1
- package/src/codegen/template.js +8 -1
- package/src/codegen/types/conversions.js +2 -2
- package/src/command-helpers/abi.js +3 -0
- package/src/command-helpers/jsonrpc.js +5 -1
- package/src/command-helpers/scaffold.js +43 -14
- package/src/command-helpers/studio.js +1 -1
- package/src/commands/add.js +7 -6
- package/src/commands/build.js +2 -2
- package/src/commands/deploy.js +15 -1
- package/src/commands/init.js +6 -6
- package/src/commands/test.js +5 -9
- 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 +4 -4
- package/src/protocols/ipfs/codegen/file_template.js +40 -0
- package/src/protocols/near/manifest.graphql +6 -0
- package/src/scaffold/ethereum.test.js +259 -0
- package/src/scaffold/index.js +15 -0
- package/src/scaffold/tests.js +196 -0
- package/src/subgraph.js +10 -4
- 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
package/src/codegen/template.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
const immutable = require('immutable')
|
|
2
|
+
const IpfsFileTemplateCodeGen = require('../protocols/ipfs/codegen/file_template')
|
|
2
3
|
|
|
3
4
|
const tsCodegen = require('./typescript')
|
|
4
5
|
|
|
5
6
|
module.exports = class DataSourceTemplateCodeGenerator {
|
|
6
7
|
constructor(template, protocol) {
|
|
7
8
|
this.template = template
|
|
8
|
-
|
|
9
|
+
let kind = template.get('kind')
|
|
10
|
+
|
|
11
|
+
if (kind.split('/')[0] == protocol.name) {
|
|
12
|
+
this.protocolTemplateCodeGen = protocol.getTemplateCodeGen(template)
|
|
13
|
+
} else if (kind == "file/ipfs") {
|
|
14
|
+
this.protocolTemplateCodeGen = new IpfsFileTemplateCodeGen(template)
|
|
15
|
+
}
|
|
9
16
|
}
|
|
10
17
|
|
|
11
18
|
generateModuleImports() {
|
|
@@ -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,9 @@ 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`
|
|
63
66
|
case "avalanche": return `https://api.snowtrace.io/api`;
|
|
64
67
|
case "fuji": return `https://api-testnet.snowtrace.io/api`;
|
|
65
68
|
default: return `https://api-${network}.etherscan.io/api`
|
|
@@ -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:') {
|
|
@@ -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,39 @@ 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
|
+
}
|
|
119
130
|
|
|
120
|
-
|
|
131
|
+
const writeTestsFiles = async (abi, protocol, contractName) => {
|
|
132
|
+
const hasEvents = protocol.hasEvents()
|
|
133
|
+
const events = hasEvents
|
|
134
|
+
? abiEvents(abi).toJS()
|
|
135
|
+
: []
|
|
136
|
+
|
|
137
|
+
if(events.length > 0) {
|
|
138
|
+
// If a contract is added to a subgraph that has no tests folder
|
|
139
|
+
await fs.ensureDir('./tests/')
|
|
140
|
+
|
|
141
|
+
const testsFiles = generateTestsFiles(contractName, events, true)
|
|
142
|
+
|
|
143
|
+
for (const [fileName, content] of Object.entries(testsFiles)) {
|
|
144
|
+
await fs.writeFile(`./tests/${fileName}`, content, {
|
|
145
|
+
encoding: 'utf-8',
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
}
|
|
121
149
|
}
|
|
122
150
|
|
|
123
151
|
module.exports = {
|
|
@@ -128,4 +156,5 @@ module.exports = {
|
|
|
128
156
|
writeABI,
|
|
129
157
|
writeSchema,
|
|
130
158
|
writeMapping,
|
|
159
|
+
writeTestsFiles,
|
|
131
160
|
}
|
package/src/commands/add.js
CHANGED
|
@@ -5,7 +5,7 @@ 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')
|
|
@@ -19,12 +19,12 @@ ${chalk.dim('Options:')}
|
|
|
19
19
|
--abi <path> Path to the contract ABI (default: download from Etherscan)
|
|
20
20
|
--contract-name Name of the contract (default: Contract)
|
|
21
21
|
--merge-entities Whether to merge entities with the same name (default: false)
|
|
22
|
-
--network-file <path> Networks file (default: "./networks.json")
|
|
22
|
+
--network-file <path> Networks config file path (default: "./networks.json")
|
|
23
23
|
-h, --help Show usage information
|
|
24
24
|
`
|
|
25
25
|
|
|
26
26
|
module.exports = {
|
|
27
|
-
description: '
|
|
27
|
+
description: 'Adds a new datasource to a subgraph',
|
|
28
28
|
run: async toolbox => {
|
|
29
29
|
// Obtain tools
|
|
30
30
|
let { print, system } = toolbox
|
|
@@ -102,9 +102,10 @@ module.exports = {
|
|
|
102
102
|
await writeABI(ethabi, contractName)
|
|
103
103
|
await writeSchema(ethabi, protocol, result.getIn(['schema', 'file']), collisionEntities)
|
|
104
104
|
await writeMapping(ethabi, protocol, contractName, collisionEntities)
|
|
105
|
+
await writeTestsFiles(ethabi, protocol, contractName)
|
|
105
106
|
|
|
106
107
|
let dataSources = result.get('dataSources')
|
|
107
|
-
let dataSource = await generateDataSource(protocol,
|
|
108
|
+
let dataSource = await generateDataSource(protocol,
|
|
108
109
|
contractName, network, address, ethabi)
|
|
109
110
|
|
|
110
111
|
// Handle the collisions edge case by copying another data source yaml data
|
|
@@ -181,12 +182,12 @@ const updateEventNamesOnCollision = (ethabi, entities, contractName, mergeEntiti
|
|
|
181
182
|
if (dataRow.get('type') === 'event'){
|
|
182
183
|
if (entities.indexOf(dataRow.get('name')) !== -1) {
|
|
183
184
|
if (entities.indexOf(`${contractName}${dataRow.get('name')}`) !== -1) {
|
|
184
|
-
print.error(`Contract name ('${contractName}')
|
|
185
|
+
print.error(`Contract name ('${contractName}')
|
|
185
186
|
+ event name ('${dataRow.get('name')}') entity already exists.`)
|
|
186
187
|
process.exitCode = 1
|
|
187
188
|
return
|
|
188
189
|
}
|
|
189
|
-
|
|
190
|
+
|
|
190
191
|
if (mergeEntities) {
|
|
191
192
|
collisionEntities.push(dataRow.get('name'))
|
|
192
193
|
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(
|
|
@@ -35,6 +36,8 @@ Options:
|
|
|
35
36
|
-o, --output-dir <path> Output directory for build results (default: build/)
|
|
36
37
|
--skip-migrations Skip subgraph migrations (default: false)
|
|
37
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")
|
|
38
41
|
`
|
|
39
42
|
|
|
40
43
|
const processForm = async (
|
|
@@ -52,7 +55,7 @@ const processForm = async (
|
|
|
52
55
|
name: 'product',
|
|
53
56
|
message: 'Product for which to deploy',
|
|
54
57
|
choices: ['subgraph-studio', 'hosted-service'],
|
|
55
|
-
skip:
|
|
58
|
+
skip:
|
|
56
59
|
product === 'subgraph-studio' ||
|
|
57
60
|
product === 'hosted-service' ||
|
|
58
61
|
studio !== undefined || node !== undefined,
|
|
@@ -101,6 +104,8 @@ module.exports = {
|
|
|
101
104
|
w,
|
|
102
105
|
watch,
|
|
103
106
|
debugFork,
|
|
107
|
+
network,
|
|
108
|
+
networkFile,
|
|
104
109
|
} = toolbox.parameters.options
|
|
105
110
|
|
|
106
111
|
// Support both long and short option variants
|
|
@@ -141,6 +146,10 @@ module.exports = {
|
|
|
141
146
|
manifest !== undefined && manifest !== ''
|
|
142
147
|
? manifest
|
|
143
148
|
: filesystem.resolve('subgraph.yaml')
|
|
149
|
+
networkFile =
|
|
150
|
+
networkFile !== undefined && networkFile !== ''
|
|
151
|
+
? networkFile
|
|
152
|
+
: filesystem.resolve("networks.json")
|
|
144
153
|
|
|
145
154
|
try {
|
|
146
155
|
const dataSourcesAndTemplates = await DataSourcesExtractor.fromFilePath(manifest)
|
|
@@ -223,6 +232,11 @@ module.exports = {
|
|
|
223
232
|
return
|
|
224
233
|
}
|
|
225
234
|
|
|
235
|
+
if (network) {
|
|
236
|
+
let identifierName = protocol.getContract().identifierName()
|
|
237
|
+
await updateSubgraphNetwork(toolbox, manifest, network, networkFile, identifierName)
|
|
238
|
+
}
|
|
239
|
+
|
|
226
240
|
const isStudio = node.match(/studio/)
|
|
227
241
|
const isHostedService = node.match(/thegraph.com/) && !isStudio
|
|
228
242
|
|
package/src/commands/init.js
CHANGED
|
@@ -827,7 +827,7 @@ const addAnotherContract = async (toolbox, { protocolInstance, directory }) => {
|
|
|
827
827
|
if (addContractConfirmation) {
|
|
828
828
|
let abiFromFile
|
|
829
829
|
let ProtocolContract = protocolInstance.getContract()
|
|
830
|
-
|
|
830
|
+
|
|
831
831
|
let questions = [
|
|
832
832
|
{
|
|
833
833
|
type: 'input',
|
|
@@ -866,16 +866,16 @@ const addAnotherContract = async (toolbox, { protocolInstance, directory }) => {
|
|
|
866
866
|
|
|
867
867
|
// Get the cwd before process.chdir in order to switch back in the end of command execution
|
|
868
868
|
const cwd = process.cwd();
|
|
869
|
-
|
|
869
|
+
|
|
870
870
|
try {
|
|
871
871
|
let { abi, contract, contractName } = await toolbox.prompt.ask(questions)
|
|
872
|
-
|
|
872
|
+
|
|
873
873
|
if (fs.existsSync(directory)) {
|
|
874
874
|
process.chdir(directory)
|
|
875
875
|
}
|
|
876
|
-
|
|
876
|
+
|
|
877
877
|
let commandLine = ['add', contract, '--contract-name', contractName]
|
|
878
|
-
|
|
878
|
+
|
|
879
879
|
if (abiFromFile) {
|
|
880
880
|
if (abi.includes(directory)) {
|
|
881
881
|
commandLine.push('--abi', path.normalize(abi.replace(directory, '')))
|
|
@@ -895,4 +895,4 @@ const addAnotherContract = async (toolbox, { protocolInstance, directory }) => {
|
|
|
895
895
|
}
|
|
896
896
|
|
|
897
897
|
return addContractConfirmation
|
|
898
|
-
}
|
|
898
|
+
}
|
package/src/commands/test.js
CHANGED
|
@@ -168,7 +168,7 @@ async function getPlatform(logsOpt) {
|
|
|
168
168
|
const type = os.type()
|
|
169
169
|
const arch = os.arch()
|
|
170
170
|
const cpuCore = os.cpus()[0]
|
|
171
|
-
const
|
|
171
|
+
const isAppleSilicon = (arch === 'arm64' && /Apple (M1|M2|processor)/.test(cpuCore.model))
|
|
172
172
|
const linuxInfo = type === 'Linux' ? await getLinuxInfo() : {}
|
|
173
173
|
const linuxDistro = linuxInfo.name
|
|
174
174
|
const release = linuxInfo.version || os.release()
|
|
@@ -178,13 +178,11 @@ async function getPlatform(logsOpt) {
|
|
|
178
178
|
print.info(`OS type: ${linuxDistro || type}\nOS arch: ${arch}\nOS release: ${release}\nOS major version: ${majorVersion}\nCPU model: ${cpuCore.model}`)
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
if (arch === 'x64' ||
|
|
181
|
+
if (arch === 'x64' || isAppleSilicon) {
|
|
182
182
|
if (type === 'Darwin') {
|
|
183
|
-
if (majorVersion === 19) {
|
|
184
|
-
return 'binary-macos-10.15'
|
|
185
|
-
} else if (
|
|
186
|
-
return 'binary-macos-10.14'
|
|
187
|
-
} else if (isM1) {
|
|
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
|
|
185
|
+
} else if (isAppleSilicon) {
|
|
188
186
|
return 'binary-macos-11-m1'
|
|
189
187
|
}
|
|
190
188
|
return 'binary-macos-11'
|
|
@@ -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
|
@@ -42,9 +42,7 @@ module.exports = class Protocol {
|
|
|
42
42
|
arweave: ['arweave-mainnet'],
|
|
43
43
|
ethereum: [
|
|
44
44
|
'mainnet',
|
|
45
|
-
'kovan',
|
|
46
45
|
'rinkeby',
|
|
47
|
-
'ropsten',
|
|
48
46
|
'goerli',
|
|
49
47
|
'poa-core',
|
|
50
48
|
'poa-sokol',
|
|
@@ -74,9 +72,11 @@ module.exports = class Protocol {
|
|
|
74
72
|
near: ['near-mainnet', 'near-testnet'],
|
|
75
73
|
cosmos: [
|
|
76
74
|
'cosmoshub-4',
|
|
77
|
-
'theta-testnet-001',
|
|
75
|
+
'theta-testnet-001', // CosmosHub testnet
|
|
78
76
|
'osmosis-1',
|
|
79
|
-
'osmo-test-4'
|
|
77
|
+
'osmo-test-4', // Osmosis testnet
|
|
78
|
+
'juno-1',
|
|
79
|
+
'uni-3' // Juno testnet
|
|
80
80
|
],
|
|
81
81
|
})
|
|
82
82
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const tsCodegen = require('../../../codegen/typescript')
|
|
2
|
+
|
|
3
|
+
module.exports = class IpfsFileTemplateCodeGen {
|
|
4
|
+
constructor(template) {
|
|
5
|
+
this.template = template
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
generateModuleImports() {
|
|
9
|
+
return []
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
generateCreateMethod() {
|
|
13
|
+
const name = this.template.get('name')
|
|
14
|
+
|
|
15
|
+
return tsCodegen.staticMethod(
|
|
16
|
+
'create',
|
|
17
|
+
[tsCodegen.param('cid', tsCodegen.namedType('string'))],
|
|
18
|
+
tsCodegen.namedType('void'),
|
|
19
|
+
`
|
|
20
|
+
DataSourceTemplate.create('${name}', [cid])
|
|
21
|
+
`,
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
generateCreateWithContextMethod() {
|
|
26
|
+
const name = this.template.get('name')
|
|
27
|
+
|
|
28
|
+
return tsCodegen.staticMethod(
|
|
29
|
+
'createWithContext',
|
|
30
|
+
[
|
|
31
|
+
tsCodegen.param('cid', tsCodegen.namedType('string')),
|
|
32
|
+
tsCodegen.param('context', tsCodegen.namedType('DataSourceContext')),
|
|
33
|
+
],
|
|
34
|
+
tsCodegen.namedType('void'),
|
|
35
|
+
`
|
|
36
|
+
DataSourceTemplate.createWithContext('${name}', [cid], context)
|
|
37
|
+
`,
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -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,13 @@ 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: this.protocol.hasEvents() ? { 'matchstick-as': `0.5.0`} : undefined,
|
|
56
59
|
}),
|
|
57
60
|
{ parser: 'json' },
|
|
58
61
|
)
|
|
@@ -137,6 +140,17 @@ dataSources:
|
|
|
137
140
|
: undefined
|
|
138
141
|
}
|
|
139
142
|
|
|
143
|
+
generateTests() {
|
|
144
|
+
const hasEvents = this.protocol.hasEvents()
|
|
145
|
+
const events = hasEvents
|
|
146
|
+
? abiEvents(this.abi).toJS()
|
|
147
|
+
: []
|
|
148
|
+
|
|
149
|
+
return events.length > 0
|
|
150
|
+
? generateTestsFiles(this.contractName, events, this.indexEvents)
|
|
151
|
+
: undefined
|
|
152
|
+
}
|
|
153
|
+
|
|
140
154
|
generate() {
|
|
141
155
|
return {
|
|
142
156
|
'package.json': this.generatePackageJson(),
|
|
@@ -145,6 +159,7 @@ dataSources:
|
|
|
145
159
|
'tsconfig.json': this.generateTsConfig(),
|
|
146
160
|
src: { [`${strings.kebabCase(this.contractName)}.ts`]: this.generateMapping() },
|
|
147
161
|
abis: this.generateABIs(),
|
|
162
|
+
tests: this.generateTests(),
|
|
148
163
|
}
|
|
149
164
|
}
|
|
150
165
|
}
|
|
@@ -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
|
+
}
|
package/src/subgraph.js
CHANGED
|
@@ -216,20 +216,26 @@ More than one template named '${name}', template names must be unique.`,
|
|
|
216
216
|
static async load(filename, { protocol, skipValidation } = { skipValidation: false }) {
|
|
217
217
|
// Load and validate the manifest
|
|
218
218
|
let data = null
|
|
219
|
+
let has_file_data_sources = false
|
|
219
220
|
|
|
220
221
|
if (filename.match(/.js$/)) {
|
|
221
222
|
data = require(path.resolve(filename))
|
|
222
223
|
} else {
|
|
223
|
-
|
|
224
|
+
let raw_data = await fs.readFile(filename, 'utf-8')
|
|
225
|
+
has_file_data_sources = raw_data.includes('kind: file')
|
|
226
|
+
data = yaml.parse(raw_data)
|
|
224
227
|
}
|
|
225
228
|
|
|
226
229
|
// Helper to resolve files relative to the subgraph manifest
|
|
227
230
|
let resolveFile = maybeRelativeFile =>
|
|
228
231
|
path.resolve(path.dirname(filename), maybeRelativeFile)
|
|
229
232
|
|
|
230
|
-
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
+
// TODO: Validation for file data sources
|
|
234
|
+
if (!has_file_data_sources) {
|
|
235
|
+
let manifestErrors = await Subgraph.validate(data, protocol, { resolveFile })
|
|
236
|
+
if (manifestErrors.size > 0) {
|
|
237
|
+
throwCombinedError(filename, manifestErrors)
|
|
238
|
+
}
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
let manifest = immutable.fromJS(data)
|
|
@@ -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
|