From 7bdf0fbbe5b810009140682ca85b13515f934eff Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 14 Jun 2019 09:06:57 +0200 Subject: [PATCH] Revert "Move contracts into api-contract" This reverts commit af0e8cf07af1bc8f710442d7302a95c165e8c97b. --- jest.config.js | 2 - packages/api-contract/LICENSE | 201 ------------------ packages/api-contract/README.md | 13 -- packages/api-contract/package.json | 32 --- packages/api-contract/src/Abi.spec.ts | 101 --------- packages/api-contract/src/Abi.ts | 29 --- packages/api-contract/src/Contract.ts | 24 --- packages/api-contract/src/index.ts | 6 - packages/api-contract/src/method.ts | 69 ------ packages/api-contract/src/types.ts | 58 ----- packages/api-contract/src/validation.ts | 50 ----- .../contracts => api/test/data}/erc20.json | 0 .../contracts => api/test/data}/erc20.wasm | Bin .../test/data}/flipper-pruned.wasm | Bin .../contracts => api/test/data}/flipper.json | 0 .../test/data}/incrementer.json | 0 .../test/data}/shared_vec-pruned.wasm | Bin .../test/data}/shared_vec.json | 0 .../api/test/e2e/promise-contract.spec.ts | 59 +++-- packages/types/src/ContractAbi.spec.ts | 82 +++++++ packages/types/src/ContractAbi.ts | 193 +++++++++++++++++ packages/types/src/index.ts | 1 + .../test/abi/types.json | 0 tsconfig.json | 2 - 24 files changed, 312 insertions(+), 610 deletions(-) delete mode 100644 packages/api-contract/LICENSE delete mode 100644 packages/api-contract/README.md delete mode 100644 packages/api-contract/package.json delete mode 100644 packages/api-contract/src/Abi.spec.ts delete mode 100644 packages/api-contract/src/Abi.ts delete mode 100644 packages/api-contract/src/Contract.ts delete mode 100644 packages/api-contract/src/index.ts delete mode 100644 packages/api-contract/src/method.ts delete mode 100644 packages/api-contract/src/types.ts delete mode 100644 packages/api-contract/src/validation.ts rename packages/{api-contract/test/contracts => api/test/data}/erc20.json (100%) rename packages/{api-contract/test/contracts => api/test/data}/erc20.wasm (100%) rename packages/{api-contract/test/contracts => api/test/data}/flipper-pruned.wasm (100%) rename packages/{api-contract/test/contracts => api/test/data}/flipper.json (100%) rename packages/{api-contract/test/contracts => api/test/data}/incrementer.json (100%) rename packages/{api-contract/test/contracts => api/test/data}/shared_vec-pruned.wasm (100%) rename packages/{api-contract/test/contracts => api/test/data}/shared_vec.json (100%) create mode 100644 packages/types/src/ContractAbi.spec.ts create mode 100644 packages/types/src/ContractAbi.ts rename packages/{api-contract => types}/test/abi/types.json (100%) diff --git a/jest.config.js b/jest.config.js index 1fdc16e75b..4ac8eb1368 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,7 +2,6 @@ const config = require('@polkadot/dev/config/jest'); module.exports = Object.assign({}, config, { moduleNameMapper: { - '@polkadot/api-contract(.*)$': '/packages/api-contract/src/$1', '@polkadot/api-derive(.*)$': '/packages/api-derive/src/$1', '@polkadot/api(.*)$': '/packages/api/src/$1', '@polkadot/rpc-(core|provider|rx)(.*)$': '/packages/rpc-$1/src/$2', @@ -14,7 +13,6 @@ module.exports = Object.assign({}, config, { modulePathIgnorePatterns: [ '/packages/api/build', '/packages/api-derive/build', - '/packages/api-contract/build', '/packages/rpc-core/build', '/packages/rpc-provider/build', '/packages/rpc-rx/build', diff --git a/packages/api-contract/LICENSE b/packages/api-contract/LICENSE deleted file mode 100644 index 0d381b2e97..0000000000 --- a/packages/api-contract/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/api-contract/README.md b/packages/api-contract/README.md deleted file mode 100644 index 46f70ad392..0000000000 --- a/packages/api-contract/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# @polkadot/api-contract - -Interfaces to allow for the encoding and decoding of Substrate contract ABIs. - -```js -import { Abi } from '@polkadot/api-contract'; - -const abi = new Abi(<...JSON ABI...>); - -api.tx.contract - .call(, , , abi.messages.(<...params...>)) - .signAndSend(, (result: SubmittableResult) => { ... }); -``` diff --git a/packages/api-contract/package.json b/packages/api-contract/package.json deleted file mode 100644 index 98b651ad84..0000000000 --- a/packages/api-contract/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@polkadot/api-contract", - "version": "0.81.0-beta.17", - "description": "Interfaces for interacting with contracts and contract ABIs", - "main": "index.js", - "keywords": [ - "Polkadot", - "ABI" - ], - "author": "Jaco Greeff ", - "maintainers": [ - "Jaco Greeff " - ], - "contributors": [], - "license": "Apache-2.0", - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/polkadot-js/api.git" - }, - "bugs": { - "url": "https://github.com/polkadot-js/api/issues" - }, - "homepage": "https://github.com/polkadot-js/api/tree/master/packages/api-contract#readme", - "dependencies": { - "@babel/runtime": "^7.4.5", - "@polkadot/types": "^0.81.0-beta.17" - } -} diff --git a/packages/api-contract/src/Abi.spec.ts b/packages/api-contract/src/Abi.spec.ts deleted file mode 100644 index c62a3e79b0..0000000000 --- a/packages/api-contract/src/Abi.spec.ts +++ /dev/null @@ -1,101 +0,0 @@ -// 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, 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' }, - { name: 'vecU32', type: 'Vec' } - ], - 'Vec' - ); - }); - - 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' } - ], - 'Option' - ); - }); - - it('has the result method with return (empty)', () => { - check( - 'result', - [], - '()' - ); - }); - - it('allows for nested args', () => { - check( - 'nested', - [ - { name: 'optionVec', type: 'Option>' } - ], - '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'] - ); - }); - }); -}); diff --git a/packages/api-contract/src/Abi.ts b/packages/api-contract/src/Abi.ts deleted file mode 100644 index c28fbb8f3c..0000000000 --- a/packages/api-contract/src/Abi.ts +++ /dev/null @@ -1,29 +0,0 @@ -// 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); - }); - } -} diff --git a/packages/api-contract/src/Contract.ts b/packages/api-contract/src/Contract.ts deleted file mode 100644 index eb87937a77..0000000000 --- a/packages/api-contract/src/Contract.ts +++ /dev/null @@ -1,24 +0,0 @@ -// 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); - } -} diff --git a/packages/api-contract/src/index.ts b/packages/api-contract/src/index.ts deleted file mode 100644 index d658180876..0000000000 --- a/packages/api-contract/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// 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'; diff --git a/packages/api-contract/src/method.ts b/packages/api-contract/src/method.ts deleted file mode 100644 index 9fccdab03d..0000000000 --- a/packages/api-contract/src/method.ts +++ /dev/null @@ -1,69 +0,0 @@ -// 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']) { - return `Option<${typeToString(type['Option'].T)}>`; - } else if (type['Result']) { - return `()`; // Result is not supported, but only applicable for returns - } else if (type['Vec']) { - return `Vec<${typeToString(type['Vec'].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, 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 & ContractABIMethodBase): ContractABIFn { - const args: Array = 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): 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; -} diff --git a/packages/api-contract/src/types.ts b/packages/api-contract/src/types.ts deleted file mode 100644 index 43298bb917..0000000000 --- a/packages/api-contract/src/types.ts +++ /dev/null @@ -1,58 +0,0 @@ -// 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: ContractABITypes - }, - 'Result'?: { - T: ContractABITypes, - E: ContractABITypes - }, - 'Vec'?: { - T: ContractABITypes - }, - '[T;n]'?: { - T: ContractABITypes, - n: number - } -}; - -export type ContractABITypes = string | ContractABITypes$Struct | Array; - -export type ContractABIArg = { - name: string, - type: ContractABITypes -}; - -export type ContractABIMethodBase = { - args: Array -}; - -export type ContractABIMethod = ContractABIMethodBase & { - mutates?: boolean, - name: string, - selector: number, - return_type: ContractABITypes | null -}; - -export type ContractABI = { - deploy: ContractABIMethodBase, - messages: Array, - name: string -}; - -export interface ContractABIFn$Arg { - name: string; - type: string; -} - -export interface ContractABIFn { - (...args: Array): Uint8Array; - args: Array; - isConstant: boolean; - type: string | null; -} diff --git a/packages/api-contract/src/validation.ts b/packages/api-contract/src/validation.ts deleted file mode 100644 index 74fe50446a..0000000000 --- a/packages/api-contract/src/validation.ts +++ /dev/null @@ -1,50 +0,0 @@ -// 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): 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); -} diff --git a/packages/api-contract/test/contracts/erc20.json b/packages/api/test/data/erc20.json similarity index 100% rename from packages/api-contract/test/contracts/erc20.json rename to packages/api/test/data/erc20.json diff --git a/packages/api-contract/test/contracts/erc20.wasm b/packages/api/test/data/erc20.wasm similarity index 100% rename from packages/api-contract/test/contracts/erc20.wasm rename to packages/api/test/data/erc20.wasm diff --git a/packages/api-contract/test/contracts/flipper-pruned.wasm b/packages/api/test/data/flipper-pruned.wasm similarity index 100% rename from packages/api-contract/test/contracts/flipper-pruned.wasm rename to packages/api/test/data/flipper-pruned.wasm diff --git a/packages/api-contract/test/contracts/flipper.json b/packages/api/test/data/flipper.json similarity index 100% rename from packages/api-contract/test/contracts/flipper.json rename to packages/api/test/data/flipper.json diff --git a/packages/api-contract/test/contracts/incrementer.json b/packages/api/test/data/incrementer.json similarity index 100% rename from packages/api-contract/test/contracts/incrementer.json rename to packages/api/test/data/incrementer.json diff --git a/packages/api-contract/test/contracts/shared_vec-pruned.wasm b/packages/api/test/data/shared_vec-pruned.wasm similarity index 100% rename from packages/api-contract/test/contracts/shared_vec-pruned.wasm rename to packages/api/test/data/shared_vec-pruned.wasm diff --git a/packages/api-contract/test/contracts/shared_vec.json b/packages/api/test/data/shared_vec.json similarity index 100% rename from packages/api-contract/test/contracts/shared_vec.json rename to packages/api/test/data/shared_vec.json diff --git a/packages/api/test/e2e/promise-contract.spec.ts b/packages/api/test/e2e/promise-contract.spec.ts index fc8f7f77ac..7832a7ae79 100644 --- a/packages/api/test/e2e/promise-contract.spec.ts +++ b/packages/api/test/e2e/promise-contract.spec.ts @@ -2,20 +2,17 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { KeyringPair } from '@polkadot/keyring/types'; - import fs from 'fs'; import path from 'path'; -import { Abi } from '@polkadot/api-contract'; +import { ContractAbi, Address, Hash } from '@polkadot/types'; +import { KeyringPair } from '@polkadot/keyring/types'; import testingPairs from '@polkadot/keyring/testingPairs'; -import { Address, Hash } from '@polkadot/types'; -import flipperAbi from '../../../api-contract/test/contracts/flipper.json'; - -import { ApiPromise, SubmittableResult } from '../../src'; - -const flipperCode = fs.readFileSync(path.join(__dirname, '../../../api-contract/test/contracts/flipper-pruned.wasm')).toString('hex'); +import Api from './../../src/promise'; +import flipperAbi from '../data/flipper.json'; +import erc20Abi from '../data/erc20.json'; +import { SubmittableResult } from './../../src'; describe.skip('Promise e2e contracts', () => { let address: Address; @@ -23,11 +20,11 @@ describe.skip('Promise e2e contracts', () => { let keyring: { [index: string]: KeyringPair }; - let api: ApiPromise; + let api: Api; beforeEach(async (done) => { if (!api) { - api = await ApiPromise.create(); + api = await Api.create(); keyring = testingPairs({ type: 'sr25519' }); } @@ -38,29 +35,31 @@ describe.skip('Promise e2e contracts', () => { describe('flipper', () => { const MAX_GAS = 500000; - let abi: Abi; + let abi: ContractAbi; beforeEach(() => { - abi = new Abi(flipperAbi); + abi = new ContractAbi(flipperAbi); }); it('allows putCode', (done) => { + const code = fs.readFileSync(path.join(__dirname, '../data/flipper-pruned.wasm')).toString('hex'); + return ( api.tx.contract - .putCode(MAX_GAS, `0x${flipperCode}`) - .signAndSend(keyring.eve, (result: SubmittableResult) => { - console.error('putCode', JSON.stringify(result)); + .putCode(MAX_GAS, `0x${code}`) + .signAndSend(keyring.eve, (result: SubmittableResult) => { + console.error('putCode', JSON.stringify(result)); - if (result.status.isFinalized) { - const record = result.findRecord('contract', 'CodeStored'); + if (result.status.isFinalized) { + const record = result.findRecord('contract', 'CodeStored'); - if (record) { - codeHash = record.event.data[0] as Hash; + if (record) { + codeHash = record.event.data[0] as Hash; - done(); - } + done(); } - }) + } + }) ); }); @@ -102,4 +101,18 @@ describe.skip('Promise e2e contracts', () => { ); }); }); + + describe('erc20', () => { + let abi: ContractAbi; + + beforeEach(() => { + abi = new ContractAbi(erc20Abi); + }); + + it('has the attached methods', () => { + expect(Object.keys(abi.messages)).toEqual( + ['totalSupply', 'balanceOf', 'allowance', 'transfer', 'approve', 'transferFrom'] + ); + }); + }); }); diff --git a/packages/types/src/ContractAbi.spec.ts b/packages/types/src/ContractAbi.spec.ts new file mode 100644 index 0000000000..348c3945aa --- /dev/null +++ b/packages/types/src/ContractAbi.spec.ts @@ -0,0 +1,82 @@ +// Copyright 2017-2019 @polkadot/types 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 TypesAbi from '../test/abi/types.json'; + +import ContractAbi, { ContractABIFn$Arg } from './ContractAbi'; + +describe('ContractAbi', () => { + let abi: ContractAbi; + + beforeAll(() => { + abi = new ContractAbi(TypesAbi); + }); + + function check (method: string, args: Array, 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' }, + { name: 'vecU32', type: 'Vec' } + ], + 'Vec' + ); + }); + + 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' } + ], + 'Option' + ); + }); + + it('has the result method with return (empty)', () => { + check( + 'result', + [], + '()' + ); + }); + + it('allows for nested args', () => { + check( + 'nested', + [ + { name: 'optionVec', type: 'Option>' } + ], + 'Vec<(u32,u64)>' + ); + }); +}); diff --git a/packages/types/src/ContractAbi.ts b/packages/types/src/ContractAbi.ts new file mode 100644 index 0000000000..2aff586092 --- /dev/null +++ b/packages/types/src/ContractAbi.ts @@ -0,0 +1,193 @@ +// Copyright 2017-2019 @polkadot/types 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 './types'; + +import { assert, isNumber, isNull, isString, isUndefined, stringCamelCase, isObject } from '@polkadot/util'; + +import Compact from './codec/Compact'; +import { createClass } from './codec/createType'; + +export type ContractABITypes$Struct = { + 'Option'?: { + T: ContractABITypes + }, + 'Result'?: { + T: ContractABITypes, + E: ContractABITypes + }, + 'Vec'?: { + T: ContractABITypes + }, + '[T;n]'?: { + T: ContractABITypes, + n: number + } +}; + +export type ContractABITypes = string | ContractABITypes$Struct | Array; + +export type ContractABIArg = { + name: string, + type: ContractABITypes +}; + +export type ContractABIMethodBase = { + args: Array +}; + +export type ContractABIMethod = ContractABIMethodBase & { + mutates?: boolean, + name: string, + selector: number, + return_type: ContractABITypes | null +}; + +export type ContractABI = { + deploy: ContractABIMethodBase, + messages: Array, + name: string +}; + +export interface ContractABIFn$Arg { + name: string; + type: string; +} + +export interface ContractABIFn { + (...args: Array): Uint8Array; + args: Array; + isConstant: boolean; + type: string | null; +} + +export interface Contract { + abi: ContractABI; + deploy: ContractABIFn; + messages: { + [index: string]: ContractABIFn + }; +} + +export function validateArgs (name: string, args: Array): 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); +} + +export default class ContractAbi implements Contract { + abi: ContractABI; + deploy: ContractABIFn; + messages: { [index: string]: ContractABIFn } = {}; + + constructor (abi: ContractABI) { + validateAbi(abi); + + this.abi = abi; + this.deploy = this._createEncoded('deploy', abi.deploy); + + abi.messages.forEach((method) => { + const name = stringCamelCase(method.name); + + this.messages[name] = this._createEncoded(`messages.${name}`, method); + }); + } + + private _convertType (type: ContractABITypes): string { + if (isString(type)) { + return type; + } else if (Array.isArray(type)) { + return `(${type.map((type) => this._convertType(type)).join(',')})`; + } else if (type['Option']) { + return `Option<${this._convertType(type['Option'].T)}>`; + } else if (type['Result']) { + return `()`; // Result is not supported, but only applicable for returns + } else if (type['Vec']) { + return `Vec<${this._convertType(type['Vec'].T)}>`; + } else if (type['[T;n]']) { + return `[${this._convertType(type['[T;n]'].T)};${type['[T;n]'].n}]`; + } + + throw new Error(`Unknown type specified ${JSON.stringify(type)}`); + } + + private _createClazz (args: Array, baseDef: { [index: string]: string }): Constructor { + return createClass( + JSON.stringify( + args.reduce((base: { [index: string]: string }, { name, type }) => { + base[name] = type; + + return base; + }, baseDef) + ) + ); + } + + private _createEncoded (name: string, method: Partial & ContractABIMethodBase): ContractABIFn { + const args: Array = method.args.map(({ name, type }) => ({ + name: stringCamelCase(name), + type: this._convertType(type) + })); + const Clazz = this._createClazz(args, isUndefined(method.selector) ? {} : { __selector: 'u32' }); + const baseStruct: { [index: string]: any } = { __selector: method.selector }; + const encoder = (...params: Array): 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 ? this._convertType(method.return_type) : null; + + return fn; + } +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index b88317cd24..4369cf4f41 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -9,5 +9,6 @@ import { assertSingletonPackage } from '@polkadot/util'; assertSingletonPackage('@polkadot/types'); +export { default as ContractAbi } from './ContractAbi'; export * from './codec'; export * from './index.types'; diff --git a/packages/api-contract/test/abi/types.json b/packages/types/test/abi/types.json similarity index 100% rename from packages/api-contract/test/abi/types.json rename to packages/types/test/abi/types.json diff --git a/tsconfig.json b/tsconfig.json index 4caefbbd9a..92edb5ea3c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,8 +7,6 @@ { "@polkadot/api": ["packages/api/src"], "@polkadot/api/*": ["packages/api/src/*"], - "@polkadot/api-contract": ["packages/api-contract/src"], - "@polkadot/api-contract/*": ["packages/api-contract/src/*"], "@polkadot/api-derive": ["packages/api-derive/src"], "@polkadot/api-derive/*": ["packages/api-derive/src/*"], "@polkadot/rpc-core": ["packages/rpc-core/src"],