Move contracts into api-contract
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// 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 { ContractABIFn$Arg } from './types';
|
||||
|
||||
import typesAbi from '../test/abi/types.json';
|
||||
import erc20Abi from '../test/contracts/erc20.json';
|
||||
|
||||
import { Abi } from '.';
|
||||
|
||||
describe('Abi', () => {
|
||||
describe('types encoding', () => {
|
||||
let abi: Abi;
|
||||
|
||||
beforeAll(() => {
|
||||
abi = new Abi(typesAbi);
|
||||
});
|
||||
|
||||
function check (method: string, args: Array<ContractABIFn$Arg>, type: string | null): void {
|
||||
const fn = abi.messages[method];
|
||||
|
||||
expect(fn.args).toEqual(args);
|
||||
expect(fn.type).toEqual(type);
|
||||
}
|
||||
|
||||
it('has the primitive method with args & return', () => {
|
||||
check(
|
||||
'primitive',
|
||||
[
|
||||
{ name: 'bool', type: 'bool' },
|
||||
{ name: 'u32', type: 'u32' }
|
||||
],
|
||||
'bool'
|
||||
);
|
||||
});
|
||||
|
||||
it('has the vector method with args & return', () => {
|
||||
check(
|
||||
'vector',
|
||||
[
|
||||
{ name: 'vecU8', type: 'Vec<u8>' },
|
||||
{ name: 'vecU32', type: 'Vec<u32>' }
|
||||
],
|
||||
'Vec<bool>'
|
||||
);
|
||||
});
|
||||
|
||||
it('has the vector_fixed method with args & return', () => {
|
||||
check(
|
||||
'vectorFixed',
|
||||
[
|
||||
{ name: 'vecU8Length32', type: '[u8;32]' }
|
||||
],
|
||||
'[u32;8]'
|
||||
);
|
||||
});
|
||||
|
||||
it('has the option method with args & return', () => {
|
||||
check(
|
||||
'option',
|
||||
[
|
||||
{ name: 'optionU32', type: 'Option<u32>' }
|
||||
],
|
||||
'Option<bool>'
|
||||
);
|
||||
});
|
||||
|
||||
it('has the result method with return (empty)', () => {
|
||||
check(
|
||||
'result',
|
||||
[],
|
||||
'()'
|
||||
);
|
||||
});
|
||||
|
||||
it('allows for nested args', () => {
|
||||
check(
|
||||
'nested',
|
||||
[
|
||||
{ name: 'optionVec', type: 'Option<Vec<u32>>' }
|
||||
],
|
||||
'Vec<(u32,u64)>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('erc20', () => {
|
||||
let abi: Abi;
|
||||
|
||||
beforeEach(() => {
|
||||
abi = new Abi(erc20Abi);
|
||||
});
|
||||
|
||||
it('has the attached methods', () => {
|
||||
expect(Object.keys(abi.messages)).toEqual(
|
||||
['totalSupply', 'balanceOf', 'allowance', 'transfer', 'approve', 'transferFrom']
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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 { ContractABI, ContractABIFn } from './types';
|
||||
|
||||
import { stringCamelCase } from '@polkadot/util';
|
||||
|
||||
import { createMethod } from './method';
|
||||
import { validateAbi } from './validation';
|
||||
|
||||
export default class ContractAbi {
|
||||
readonly abi: ContractABI;
|
||||
readonly deploy: ContractABIFn;
|
||||
readonly messages: { [index: string]: ContractABIFn } = {};
|
||||
|
||||
constructor (abi: ContractABI) {
|
||||
validateAbi(abi);
|
||||
|
||||
this.abi = abi;
|
||||
this.deploy = createMethod('deploy', abi.deploy);
|
||||
|
||||
abi.messages.forEach((method) => {
|
||||
const name = stringCamelCase(method.name);
|
||||
|
||||
this.messages[name] = createMethod(`messages.${name}`, method);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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 { ContractABI } from './types';
|
||||
|
||||
import { AccountId } from '@polkadot/types';
|
||||
|
||||
import Abi from './Abi';
|
||||
|
||||
export default class Contract {
|
||||
readonly abi: Abi;
|
||||
readonly address: AccountId;
|
||||
|
||||
// TODO This needs to be useful
|
||||
// - pass API instance
|
||||
// - expose calls here so we don't need to do the encoding manually
|
||||
//
|
||||
// Also, we need the same structure for managing code and deploying
|
||||
constructor (address: string | AccountId, abi: ContractABI) {
|
||||
this.abi = new Abi(abi);
|
||||
this.address = new AccountId(address);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// 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.
|
||||
|
||||
export { default as Abi } from './Abi';
|
||||
export { default as Contract } from './Contract';
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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, Constructor } from '@polkadot/types/types';
|
||||
import { ContractABIFn, ContractABIFn$Arg, ContractABIMethod, ContractABIMethodBase, ContractABITypes } from './types';
|
||||
|
||||
import { Compact, createClass } from '@polkadot/types';
|
||||
import { assert, isString, isUndefined, stringCamelCase } from '@polkadot/util';
|
||||
|
||||
export function typeToString (type: ContractABITypes): string {
|
||||
if (isString(type)) {
|
||||
return type;
|
||||
} else if (Array.isArray(type)) {
|
||||
return `(${type.map((type) => typeToString(type)).join(',')})`;
|
||||
} else if (type['Option<T>']) {
|
||||
return `Option<${typeToString(type['Option<T>'].T)}>`;
|
||||
} else if (type['Result<T,E>']) {
|
||||
return `()`; // Result is not supported, but only applicable for returns
|
||||
} else if (type['Vec<T>']) {
|
||||
return `Vec<${typeToString(type['Vec<T>'].T)}>`;
|
||||
} else if (type['[T;n]']) {
|
||||
return `[${typeToString(type['[T;n]'].T)};${type['[T;n]'].n}]`;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown type specified ${JSON.stringify(type)}`);
|
||||
}
|
||||
|
||||
export function createArgClass (args: Array<ContractABIFn$Arg>, baseDef: { [index: string]: string }): Constructor {
|
||||
return createClass(
|
||||
JSON.stringify(
|
||||
args.reduce((base: { [index: string]: string }, { name, type }) => {
|
||||
base[name] = type;
|
||||
|
||||
return base;
|
||||
}, baseDef)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function createMethod (name: string, method: Partial<ContractABIMethod> & ContractABIMethodBase): ContractABIFn {
|
||||
const args: Array<ContractABIFn$Arg> = method.args.map(({ name, type }) => ({
|
||||
name: stringCamelCase(name),
|
||||
type: typeToString(type)
|
||||
}));
|
||||
const Clazz = createArgClass(args, isUndefined(method.selector) ? {} : { __selector: 'u32' });
|
||||
const baseStruct: { [index: string]: any } = { __selector: method.selector };
|
||||
const encoder = (...params: Array<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) => {
|
||||
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.return_type ? typeToString(method.return_type) : null;
|
||||
|
||||
return fn;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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 } from '@polkadot/types/types';
|
||||
|
||||
export type ContractABITypes$Struct = {
|
||||
'Option<T>'?: {
|
||||
T: ContractABITypes
|
||||
},
|
||||
'Result<T,E>'?: {
|
||||
T: ContractABITypes,
|
||||
E: ContractABITypes
|
||||
},
|
||||
'Vec<T>'?: {
|
||||
T: ContractABITypes
|
||||
},
|
||||
'[T;n]'?: {
|
||||
T: ContractABITypes,
|
||||
n: number
|
||||
}
|
||||
};
|
||||
|
||||
export type ContractABITypes = string | ContractABITypes$Struct | Array<string | ContractABITypes$Struct>;
|
||||
|
||||
export type ContractABIArg = {
|
||||
name: string,
|
||||
type: ContractABITypes
|
||||
};
|
||||
|
||||
export type ContractABIMethodBase = {
|
||||
args: Array<ContractABIArg>
|
||||
};
|
||||
|
||||
export type ContractABIMethod = ContractABIMethodBase & {
|
||||
mutates?: boolean,
|
||||
name: string,
|
||||
selector: number,
|
||||
return_type: ContractABITypes | null
|
||||
};
|
||||
|
||||
export type ContractABI = {
|
||||
deploy: ContractABIMethodBase,
|
||||
messages: Array<ContractABIMethod>,
|
||||
name: string
|
||||
};
|
||||
|
||||
export interface ContractABIFn$Arg {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface ContractABIFn {
|
||||
(...args: Array<CodecArg>): Uint8Array;
|
||||
args: Array<ContractABIFn$Arg>;
|
||||
isConstant: boolean;
|
||||
type: string | null;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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 { ContractABI, ContractABIArg } from './types';
|
||||
|
||||
import { assert, isNumber, isNull, isString, isObject } from '@polkadot/util';
|
||||
|
||||
export function validateArgs (name: string, args: Array<ContractABIArg>): void {
|
||||
assert(Array.isArray(args), `Expected 'args' to exist on ${name}`);
|
||||
|
||||
args.forEach((arg) => {
|
||||
const unknownKeys = Object.keys(arg).filter((key) => !['name', 'type'].includes(key));
|
||||
|
||||
assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI args for ${name}`);
|
||||
assert(isString(arg.name), `${name} args should have valid name `);
|
||||
assert(isString(arg.type) || isObject(arg.type), `${name} args should have valid type`);
|
||||
});
|
||||
}
|
||||
|
||||
export function validateDeploy ({ deploy }: ContractABI): void {
|
||||
const unknownKeys = Object.keys(deploy).filter((key) => !['args'].includes(key));
|
||||
|
||||
assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI deploy`);
|
||||
|
||||
validateArgs('deploy', deploy.args);
|
||||
}
|
||||
|
||||
export function validateMethods ({ messages }: ContractABI): void {
|
||||
messages.forEach((method) => {
|
||||
const unknownKeys = Object.keys(method).filter((key) => !['args', 'mutates', 'name', 'selector', 'return_type'].includes(key));
|
||||
|
||||
assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI args for messages.${method.name}`);
|
||||
assert(isString(method.name), `Expected name for messages.${method.name}`);
|
||||
assert(isNumber(method.selector), `Expected selector for messages.${method.name}`);
|
||||
assert(isNull(method.return_type) || isString(method.return_type) || isObject(method.return_type), `Expected return_type for messages.${method.name}`);
|
||||
|
||||
validateArgs(`messages.${method.name}`, method.args);
|
||||
});
|
||||
}
|
||||
|
||||
export function validateAbi (abi: ContractABI): void {
|
||||
const unknownKeys = Object.keys(abi).filter((key) => !['deploy', 'messages', 'name'].includes(key));
|
||||
|
||||
assert(unknownKeys.length === 0, `Unknown fields ${unknownKeys.join(', ')} found in ABI`);
|
||||
assert(abi.deploy && abi.messages && isString(abi.name), 'ABI should have deploy, messages & name sections');
|
||||
|
||||
validateDeploy(abi);
|
||||
validateMethods(abi);
|
||||
}
|
||||
Reference in New Issue
Block a user