Files
api/packages/api-contract/src/ContractRegistry.ts
T
kwingram25 05ec4964fe Generate types from new contract ABI JSON (#1320)
* generate types from registry

* cleanup

* merge, rearrange

* lint

* built out

* abi v2 creation

* throw on unions

* linting

* version handling

* split functions

* cleanup

* drop v1 support

* special types

* changes

* requested changes

* formatTYpe

* rv outdated test

* rv union test

* codeclimate

* codeclimate

* codeclimate

* codeclimate

* codeclimate

* review

* cleanup encodeTypes

* fix option sub

* TypeDefInfo.Result
2019-10-08 16:31:48 +02:00

176 lines
7.4 KiB
TypeScript

// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { CodecArg, TypeDef } from '@polkadot/types/types';
import { ContractABIArgBasePre, ContractABIContract, ContractABIContractPre, ContractABIEvent, ContractABIEventPre, ContractABIFn, ContractABIFnArg, ContractABIMethod, ContractABIMethodBase, ContractABIMethodPre, ContractABI, ContractABIPre, ContractABIRange, ContractABIRangePre, ContractABIStorage, ContractABIStorageLayout, ContractABIStorageLayoutPre, ContractABIStoragePre, ContractABIStorageStruct, ContractABIStorageStructPre, ContractABITypePre } from './types';
import { Compact } from '@polkadot/types';
import { assert, isNumber, isString, isNull, isObject, isUndefined, stringCamelCase } from '@polkadot/util';
import MetaRegistry from './MetaRegistry';
import { createArgClass } from './method';
export default class ContractRegistry extends MetaRegistry {
// Contract ABI helpers
public validateArgs (name: string, args: ContractABIArgBasePre[]): void {
assert(Array.isArray(args), `Expected 'args' to exist on ${name}`);
args.forEach((arg): void => {
const unknownKeys = Object.keys(arg).filter((key): boolean => !['name', 'type'].includes(key));
assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI args for ${name}`);
assert(isNumber(arg.name) && isString(this.stringAt(arg.name)), `${name} args should have valid name `);
assert(isNumber(arg.type.ty) && isObject(this.typeDefAt(arg.type.ty)), `${name} args should have valid type`);
});
}
public validateConstructors ({ contract: { constructors } }: ContractABIPre): void {
constructors.forEach((constructor: ContractABIMethodPre, index): void => {
const unknownKeys = Object.keys(constructor).filter((key): boolean => !['args', 'docs', 'name', 'selector'].includes(key));
assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI constructor`);
this.validateArgs(`constructor ${index}`, constructor.args);
});
}
public validateMethods ({ contract: { messages } }: ContractABIPre): void {
messages.forEach((method): void => {
const unknownKeys = Object.keys(method).filter((key): boolean => !['args', 'docs', 'mutates', 'name', 'selector', 'return_type'].includes(key));
assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI args for messages.${method.name}`);
const { name, selector, return_type: returnType } = method;
assert(isNumber(name) && isString(this.stringAt(name)), `Expected name for messages.${method.name}`);
assert(isNumber(selector), `Expected selector for messages.${method.name}`);
assert(isNull(returnType) || (isNumber(returnType.ty) && isObject(this.typeDefAt(returnType.ty))), `Expected return_type for messages.${method.name}`);
this.validateArgs(`messages.${method.name}`, method.args);
});
}
public validateAbi (abi: ContractABIPre): void {
const unknownKeys = Object.keys(abi.contract).filter((key): boolean => !['constructors', 'docs', 'events', 'messages', 'name'].includes(key));
assert(unknownKeys.length === 0, `Unknown fields ${unknownKeys.join(', ')} found in ABI`);
const { contract: { constructors, messages, name } } = abi;
assert(constructors && messages && isString(this.stringAt(name)), 'ABI should have constructors, messages & name sections');
this.validateConstructors(abi);
this.validateMethods(abi);
}
public createMethod (name: string, method: Partial<ContractABIMethod> & ContractABIMethodBase): ContractABIFn {
const args = method.args.map(({ name, type }): ContractABIFnArg => {
assert(isObject(type), `Invalid type at index ${type}`);
return {
name: stringCamelCase(name),
type
};
});
const Clazz = createArgClass(args, isUndefined(method.selector) ? {} : { __selector: 'u32' });
const baseStruct: { [index: string]: any } = { __selector: method.selector };
const encoder = (...params: CodecArg[]): Uint8Array => {
assert(params.length === args.length, `Expected ${args.length} arguments to contract ${name}, found ${params.length}`);
const u8a = new Clazz(
args.reduce((mapped, { name }, index): Record<string, CodecArg> => {
mapped[name] = params[index];
return mapped;
}, { ...baseStruct })
).toU8a();
return Compact.addLengthPrefix(u8a);
};
const fn = (encoder as ContractABIFn);
fn.args = args;
fn.isConstant = !(method.mutates || false);
fn.type = method.returnType || null;
return fn;
}
public convertAbi ({ contract, storage }: ContractABIPre): ContractABI {
return {
contract: this.convertContract(contract),
storage: this.convertStorage(storage)
};
}
public convertArgs (args: ContractABIArgBasePre[]): any[] {
return args.map(({ name, type, ...arg }) => ({ ...arg, name: this.stringAt(name), type: this.convertType(type) }));
}
public convertType ({ ty, display_name: displayNameIndices }: ContractABITypePre): TypeDef {
const displayName = this.stringsAt(displayNameIndices).join('::');
return this.typeDefAt(ty, { displayName });
}
public convertContract ({ constructors, messages, name, events, ...contract }: ContractABIContractPre): ContractABIContract {
return {
constructors: this.convertConstructors(constructors),
messages: messages.map(method => this.convertMethod(method)),
name: this.stringAt(name),
...(events
? { events: events.map(event => this.convertEvent(event)) }
: {}),
...contract
};
}
public convertConstructors (constructors: ContractABIMethodPre[]): ContractABIMethod[] {
return constructors.map(
(constructor: ContractABIMethodPre): ContractABIMethod => {
return this.convertMethod(constructor);
}
);
}
public convertMethod ({ args, name, return_type: returnType, ...method }: ContractABIMethodPre): ContractABIMethod {
return {
...method,
args: this.convertArgs(args),
name: this.stringAt(name),
returnType: returnType ? this.convertType(returnType) : null
};
}
public convertEvent ({ args }: ContractABIEventPre): ContractABIEvent {
return {
args: this.convertArgs(args)
};
}
public convertStorage (storage: ContractABIStoragePre): ContractABIStorage {
return this.convertStorageStruct(storage);
}
public convertStorageLayout (storageLayout: ContractABIStorageLayoutPre): ContractABIStorageLayout {
if ((storageLayout as ContractABIStorageStructPre)['struct.type']) {
return this.convertStorageStruct(storageLayout as ContractABIStorageStructPre);
} else {
return this.convertStorageRange(storageLayout as ContractABIRangePre);
}
}
public convertStorageStruct ({ 'struct.type': structType, 'struct.fields': structFields }: ContractABIStorageStructPre): ContractABIStorageStruct {
return {
'struct.type': this.typeDefAt(structType),
'struct.fields': structFields.map(({ name, layout }) => ({
name: this.stringAt(name),
layout: this.convertStorageLayout(layout)
}))
};
}
public convertStorageRange ({ 'range.elem_type': type, ...range }: ContractABIRangePre): ContractABIRange {
return {
...range,
'range.elem_type': this.typeDefAt(type)
};
}
}