UNPKG

@graphprotocol/graph-cli

Version:

CLI for building for and deploying to The Graph

299 lines (298 loc) • 12.9 kB
import path from 'node:path'; import fs from 'fs-extra'; import immutable from 'immutable'; import { afterAll, beforeAll, describe, expect, test } from 'vitest'; import * as ts from '../../../codegen/typescript.js'; import ABI from '../abi.js'; import AbiCodeGenerator from './abi.js'; let tempdir; let abi; let generatedTypes; describe.concurrent('ABI code generation', () => { beforeAll(async () => { tempdir = await fs.mkdtemp('abi-codegen'); try { const filename = path.join(tempdir, 'ABI.json'); await fs.writeFile(filename, JSON.stringify([ { constant: true, inputs: [], name: 'read', outputs: [{ name: '', type: 'bytes32' }], payable: false, type: 'function', }, { constant: true, inputs: [ { name: 'proposalId', type: 'uint256', }, { components: [ { name: 'foo', type: 'uint8', }, { name: 'bar', type: 'tuple', components: [{ name: 'baz', type: 'address' }], }, ], name: '', type: 'tuple', }, ], name: 'getProposal', outputs: [ { components: [ { name: 'result', type: 'uint8', }, { name: 'target', type: 'address', }, { name: 'data', type: 'bytes', }, { name: 'proposer', type: 'address', }, { name: 'feeRecipient', type: 'address', }, { name: 'fee', type: 'uint256', }, { name: 'startTime', type: 'uint256', }, { name: 'yesCount', type: 'uint256', }, { name: 'noCount', type: 'uint256', }, ], name: '', type: 'tuple', }, ], payable: false, stateMutability: 'view', type: 'function', }, { type: 'function', stateMutability: 'view', payable: 'false', name: 'getProposals', outputs: [ { type: 'uint256', name: 'size', }, { type: 'tuple[]', components: [ { name: 'first', type: 'uint256' }, { name: 'second', type: 'string' }, ], }, ], }, { type: 'function', stateMutability: 'view', name: 'overloaded', inputs: [ { type: 'string', }, ], outputs: [ { type: 'string', }, ], }, { type: 'function', stateMutability: 'view', name: 'overloaded', inputs: [ { type: 'uint256', }, ], outputs: [ { type: 'string', }, ], }, { type: 'function', stateMutability: 'view', name: 'overloaded', inputs: [ { type: 'bytes32', }, ], outputs: [ { type: 'string', }, ], }, ]), 'utf-8'); abi = ABI.load('Contract', filename); const codegen = new AbiCodeGenerator(abi); generatedTypes = await codegen.generateTypes(); } finally { await fs.remove(tempdir); } }); afterAll(async () => { await fs.remove(tempdir); }); describe('Generated types', () => { test('All expected types are generated', () => { expect(generatedTypes.map(type => type.name)).toEqual([ 'Contract__getProposalResultValue0Struct', 'Contract__getProposalInputParam1Struct', 'Contract__getProposalInputParam1BarStruct', 'Contract__getProposalsResultValue1Struct', 'Contract__getProposalsResult', 'Contract', ]); }); }); describe('Contract class', () => { test('Exists', () => { expect(generatedTypes.find(type => type.name === 'Contract')).toBeDefined(); }); test('Has methods', () => { const contract = generatedTypes.find(type => type.name === 'Contract'); expect(contract.methods).toBeInstanceOf(Array); }); test('Has `bind` method', () => { const contract = generatedTypes.find(type => type.name === 'Contract'); expect(contract.methods.find((method) => method.name === 'bind')).toBeDefined(); }); test('Has methods for all callable functions', () => { const contract = generatedTypes.find(type => type.name === 'Contract'); expect(contract.methods.map((method) => method.name)).toContain('getProposal'); }); }); describe('Methods for callable functions', () => { test('Have correct parameters', () => { const contract = generatedTypes.find(type => type.name === 'Contract'); expect(contract.methods.map((method) => [method.name, method.params])).toEqual([ ['bind', immutable.List([ts.param('address', 'Address')])], ['read', immutable.List()], ['try_read', immutable.List()], [ 'getProposal', immutable.List([ ts.param('proposalId', 'BigInt'), ts.param('param1', 'Contract__getProposalInputParam1Struct'), ]), ], [ 'try_getProposal', immutable.List([ ts.param('proposalId', 'BigInt'), ts.param('param1', 'Contract__getProposalInputParam1Struct'), ]), ], ['getProposals', immutable.List()], ['try_getProposals', immutable.List()], ['overloaded', immutable.List([ts.param('param0', 'string')])], ['try_overloaded', immutable.List([ts.param('param0', 'string')])], ['overloaded1', immutable.List([ts.param('param0', 'BigInt')])], ['try_overloaded1', immutable.List([ts.param('param0', 'BigInt')])], ['overloaded2', immutable.List([ts.param('param0', 'Bytes')])], ['try_overloaded2', immutable.List([ts.param('param0', 'Bytes')])], ]); }); test('Have correct return types', () => { const contract = generatedTypes.find(type => type.name === 'Contract'); expect(contract.methods.map((method) => [method.name, method.returnType])).toEqual([ ['bind', ts.namedType('Contract')], ['read', ts.namedType('Bytes')], ['try_read', 'ethereum.CallResult<Bytes>'], ['getProposal', ts.namedType('Contract__getProposalResultValue0Struct')], ['try_getProposal', 'ethereum.CallResult<Contract__getProposalResultValue0Struct>'], ['getProposals', ts.namedType('Contract__getProposalsResult')], ['try_getProposals', 'ethereum.CallResult<Contract__getProposalsResult>'], ['overloaded', ts.namedType('string')], ['try_overloaded', 'ethereum.CallResult<string>'], ['overloaded1', ts.namedType('string')], ['try_overloaded1', 'ethereum.CallResult<string>'], ['overloaded2', ts.namedType('string')], ['try_overloaded2', 'ethereum.CallResult<string>'], ]); }); }); describe('Tuples', () => { test('Tuple types exist for function parameters', () => { let tupleType = generatedTypes.find(type => type.name === 'Contract__getProposalInputParam1Struct'); // Verify that the tuple type has methods expect(tupleType.methods).toBeDefined(); // Verify that the tuple type has getters for all tuple fields with // the right return types expect(tupleType.methods.map((method) => [method.name, method.returnType])).toEqual([ ['get foo', 'i32'], ['get bar', 'Contract__getProposalInputParam1BarStruct'], ]); // Inner tuple: tupleType = generatedTypes.find(type => type.name === 'Contract__getProposalInputParam1BarStruct'); // Verify that the tuple type has methods expect(tupleType.methods).toBeDefined(); // Verify that the tuple type has getters for all tuple fields with // the right return types expect(tupleType.methods.map((method) => [method.name, method.returnType])).toEqual([ ['get baz', 'Address'], ]); }); test('Tuple types exist for function return values', () => { const tupleType = generatedTypes.find(type => type.name === 'Contract__getProposalResultValue0Struct'); // Verify that the tuple type has methods expect(tupleType.methods).toBeDefined(); // Verify that the tuple type has getters for all tuple fields with // the right return types expect(tupleType.methods.map((method) => [method.name, method.returnType])).toEqual([ ['get result', 'i32'], ['get target', 'Address'], ['get data', 'Bytes'], ['get proposer', 'Address'], ['get feeRecipient', 'Address'], ['get fee', 'BigInt'], ['get startTime', 'BigInt'], ['get yesCount', 'BigInt'], ['get noCount', 'BigInt'], ]); }); test('Function bodies are generated correctly for tuple arrays', () => { const contract = generatedTypes.find(type => type.name === 'Contract'); const getter = contract.methods.find((method) => method.name === 'getProposals'); expect(getter.body).not.toContain('toTupleArray<undefined>'); expect(getter.body).toContain('result[1].toTupleArray<Contract__getProposalsResultValue1Struct>()'); }); }); });