@graphprotocol/graph-cli
Version:
CLI for building for and deploying to The Graph
160 lines (140 loc) • 4.75 kB
JavaScript
const fs = require('fs-extra')
const path = require('path')
const AbiCodeGenerator = require('./codegen/abi')
const TUPLE_ARRAY_PATTERN = /^tuple\[([0-9]*)\]$/
const TUPLE_MATRIX_PATTERN = /^tuple\[([0-9]*)\]\[([0-9]*)\]$/
const buildOldSignatureParameter = input => {
return input.get('type') === 'tuple'
? `(${input
.get('components')
.map(component => buildSignatureParameter(component))
.join(',')})`
: `${input.get('type')}`
}
const buildSignatureParameter = input => {
if (input.get('type') === 'tuple') {
return `(${input.get('indexed') ? 'indexed ' : ''}${input
.get('components')
.map(component => buildSignatureParameter(component))
.join(',')})`
} else if (input.get('type').match(TUPLE_ARRAY_PATTERN)) {
const length = input.get('type').match(TUPLE_ARRAY_PATTERN)[1]
return `(${input.get('indexed') ? 'indexed ' : ''}${input
.get('components')
.map(component => buildSignatureParameter(component))
.join(',')})[${length ? length : ''}]`
} else if (input.get('type').match(TUPLE_MATRIX_PATTERN)) {
const length1 = input.get('type').match(TUPLE_MATRIX_PATTERN)[1]
const length2 = input.get('type').match(TUPLE_MATRIX_PATTERN)[2]
return `(${input.get('indexed') ? 'indexed ' : ''}${input
.get('components')
.map(component => buildSignatureParameter(component))
.join(',')})[${length1 ? length1 : ''}][${length2 ? length2 : ''}]`
} else {
return `${input.get('indexed') ? 'indexed ' : ''}${input.get('type')}`
}
}
module.exports = class ABI {
constructor(name, file, data) {
this.name = name
this.file = file
this.data = data
}
codeGenerator() {
return new AbiCodeGenerator(this)
}
static oldEventSignature(event) {
return `${event.get('name')}(${event
.get('inputs', [])
.map(buildOldSignatureParameter)
.join(',')})`
}
static eventSignature(event) {
return `${event.get('name')}(${event
.get('inputs', [])
.map(buildSignatureParameter)
.join(',')})`
}
/**
* For the ABI of a function, returns a string function signature compatible
* with the Rust `ethabi` library. It is of the form
*
* <function>([<input-type-1>, ...])[:(<output-type-1,...)]
*
* A few examples for a function called `example`:
*
* - No inputs or outputs: `example()`
* - One input and output: `example(uint256):(bool)`
* - Multiple inputs and outputs: `example(uint256,(string,bytes32)):(bool,uint256)`
*/
functionSignature(fn) {
let inputs = fn
.get('inputs', [])
.map(buildSignatureParameter)
.join(',')
let outputs = fn
.get('outputs', [])
.map(buildSignatureParameter)
.join(',')
return `${fn.get('name')}(${inputs})${outputs.length > 0 ? `:(${outputs})` : ''}`
}
oldEventSignatures() {
return this.data
.filter(entry => entry.get('type') === 'event')
.map(ABI.oldEventSignature)
}
eventSignatures() {
return this.data
.filter(entry => entry.get('type') === 'event')
.map(ABI.eventSignature)
}
callFunctions() {
// An entry is a function if its type is not set or if it is one of
// 'constructor', 'function' or 'fallback'
let functionTypes = new Set(['constructor', 'function', 'fallback'])
let functions = this.data.filter(
entry => !entry.has('type') || functionTypes.has(entry.get('type')),
)
// A function is a call function if it is nonpayable, payable or
// not constant
let mutabilityTypes =new Set(['nonpayable', 'payable'])
return functions.filter(
entry =>
mutabilityTypes.has(entry.get('stateMutability')) ||
entry.get('constant') === false,
)
}
callFunctionSignatures() {
return this.callFunctions()
.filter(entry => entry.get('type') !== 'constructor')
.map(entry => {
const name = entry.get('name', '<default>')
const inputs = entry
.get('inputs', [])
.map(input => buildSignatureParameter(input))
return `${name}(${inputs.join(',')})`
})
}
static normalized(json) {
if (Array.isArray(json)) {
return json
} else if (json.abi !== undefined) {
return json.abi
} else if (
json.compilerOutput !== undefined &&
json.compilerOutput.abi !== undefined
) {
return json.compilerOutput.abi
} else {
return undefined
}
}
static load(name, file) {
let data = JSON.parse(fs.readFileSync(file))
let abi = ABI.normalized(data)
if (abi === null || abi === undefined) {
throw Error(`No valid ABI in file: ${path.relative(process.cwd(), file)}`)
}
return new ABI(name, file, abi)
}
}