diff --git a/packages/api-contract/src/Abi/index.ts b/packages/api-contract/src/Abi/index.ts index 090db4c257..897b4e0188 100644 --- a/packages/api-contract/src/Abi/index.ts +++ b/packages/api-contract/src/Abi/index.ts @@ -10,7 +10,7 @@ import { TypeRegistry } from '@polkadot/types'; import { TypeDefInfo } from '@polkadot/types-create'; import { assert, assertReturn, compactAddLength, compactStripLength, isNumber, isObject, isString, logger, stringCamelCase, stringify, u8aConcat, u8aToHex } from '@polkadot/util'; -import { v0ToLatest, v1ToLatest } from './toLatest'; +import { convertVersions, enumVersions } from './toLatest'; const l = logger('Abi'); @@ -26,23 +26,18 @@ function findMessage (list: T[], messageOrId: T | string return assertReturn(message, () => `Attempted to call an invalid contract interface, ${stringify(messageOrId)}`); } -// FIXME: This is still workable with V0, V1 & V2, but certainly is not a scalable -// approach (right at this point don't quite have better ideas that is not as complex -// as the conversion tactics in the runtime Metadata) function getLatestMeta (registry: Registry, json: Record): ContractMetadataLatest { + const vx = enumVersions.find((v) => isObject(json[v])); const metadata = registry.createType('ContractMetadata', - isObject(json.V2) - ? { V2: json.V2 } - : isObject(json.V1) - ? { V1: json.V1 } - : { V0: json } + vx + ? { [vx]: json[vx] } + : { V0: json } ) as unknown as ContractMetadata; + const converter = convertVersions.find(([v]) => metadata[`is${v}`]); - return metadata.isV2 - ? metadata.asV2 - : metadata.isV1 - ? v1ToLatest(registry, metadata.asV1) - : v0ToLatest(registry, metadata.asV0); + assert(converter, () => `Unable to convert ABI with version ${metadata.type} to latest`); + + return converter[1](registry, metadata[`as${converter[0]}`]); } function parseJson (json: Record, chainProperties?: ChainProperties): [Record, Registry, ContractMetadataLatest, ContractProjectInfo] { @@ -90,7 +85,8 @@ export class Abi { ); this.constructors = this.metadata.spec.constructors.map((spec: ContractConstructorSpecLatest, index) => this.#createMessage(spec, index, { - isConstructor: true + isConstructor: true, + isPayable: spec.payable.isTrue }) ); this.events = this.metadata.spec.events.map((spec: ContractEventSpecLatest, index) => diff --git a/packages/api-contract/src/Abi/toLatest.spec.ts b/packages/api-contract/src/Abi/toLatest.spec.ts index a190c0a0f0..8be2005ba7 100644 --- a/packages/api-contract/src/Abi/toLatest.spec.ts +++ b/packages/api-contract/src/Abi/toLatest.spec.ts @@ -1,7 +1,7 @@ // Copyright 2017-2022 @polkadot/api-contract authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { v0ToLatest, v1ToLatest } from '@polkadot/api-contract/Abi/toLatest'; +import { v0ToLatest, v1ToLatest, v2ToLatest } from '@polkadot/api-contract/Abi/toLatest'; import { TypeRegistry } from '@polkadot/types'; import abis from '../test/contracts'; @@ -71,3 +71,15 @@ describe('v1ToLatest', (): void => { ).toEqual(['init_value']); }); }); + +describe('v2ToLatest', (): void => { + const registry = new TypeRegistry(); + const contract = registry.createType('ContractMetadata', { V2: abis.ink_v2_flipper.V2 }); + const latest = v2ToLatest(registry, contract.asV2); + + it('has the correct constructor flag', (): void => { + expect( + latest.spec.constructors[0].payable.isTrue + ).toEqual(true); + }); +}); diff --git a/packages/api-contract/src/Abi/toLatest.ts b/packages/api-contract/src/Abi/toLatest.ts index 5d920235e7..1413a6905a 100644 --- a/packages/api-contract/src/Abi/toLatest.ts +++ b/packages/api-contract/src/Abi/toLatest.ts @@ -1,16 +1,38 @@ // Copyright 2017-2022 @polkadot/api-contract authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { ContractMetadataLatest, ContractMetadataV0, ContractMetadataV1 } from '@polkadot/types/interfaces'; +import type { ContractMetadataLatest, ContractMetadataV3 } from '@polkadot/types/interfaces'; import type { Registry } from '@polkadot/types/types'; import { v0ToV1 } from './toV1'; import { v1ToV2 } from './toV2'; +import { v2ToV3 } from './toV3'; -export function v1ToLatest (registry: Registry, v1: ContractMetadataV1): ContractMetadataLatest { - return v1ToV2(registry, v1); +type Versions = 'V3' | 'V2' | 'V1' | 'V0'; + +type Converter = (registry: Registry, vx: any) => ContractMetadataLatest; + +// Helper to convert metadata from one step to the next +function createConverter (next: (registry: Registry, input: O) => ContractMetadataLatest, step: (registry: Registry, input: I) => O): (registry: Registry, input: I) => ContractMetadataLatest { + return (registry: Registry, input: I): ContractMetadataLatest => + next(registry, step(registry, input)); } -export function v0ToLatest (registry: Registry, v0: ContractMetadataV0): ContractMetadataLatest { - return v1ToLatest(registry, v0ToV1(registry, v0)); +export function v3ToLatest (registry: Registry, v3: ContractMetadataV3): ContractMetadataLatest { + return v3; } + +export const v2ToLatest = createConverter(v3ToLatest, v2ToV3); +export const v1ToLatest = createConverter(v2ToLatest, v1ToV2); +export const v0ToLatest = createConverter(v1ToLatest, v0ToV1); + +// The versions where an enum is used, aka V0 is missing +// (Order from newest, i.e. we expect more on newest vs oldest) +export const enumVersions = ['V3', 'V2', 'V1']; + +export const convertVersions: [Versions, Converter][] = [ + ['V3', v3ToLatest], + ['V2', v2ToLatest], + ['V1', v1ToLatest], + ['V0', v0ToLatest] +]; diff --git a/packages/api-contract/src/Abi/toV3.ts b/packages/api-contract/src/Abi/toV3.ts new file mode 100644 index 0000000000..a283b3bcb5 --- /dev/null +++ b/packages/api-contract/src/Abi/toV3.ts @@ -0,0 +1,18 @@ +// Copyright 2017-2022 @polkadot/api-contract authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { ContractMetadataV2, ContractMetadataV3 } from '@polkadot/types/interfaces'; +import type { Registry } from '@polkadot/types/types'; + +import { objectSpread } from '@polkadot/util'; + +export function v2ToV3 (registry: Registry, v2: ContractMetadataV2): ContractMetadataV3 { + return registry.createType('ContractMetadataV3', objectSpread({}, v2, { + spec: objectSpread({}, v2.spec, { + constructors: v2.spec.constructors.map((c) => + // V3 introduces the payable flag on constructors, for ', docs: 'Vec' }, + ContractConstructorSpecV3: { + label: 'Text', + selector: 'ContractSelector', + payable: 'bool', + args: 'Vec', + docs: 'Vec' + }, ContractContractSpecV0: { constructors: 'Vec', messages: 'Vec', @@ -81,6 +88,12 @@ const spec = { events: 'Vec', docs: 'Vec' }, + ContractContractSpecV3: { + constructors: 'Vec', + messages: 'Vec', + events: 'Vec', + docs: 'Vec' + }, ContractDisplayName: 'SiPath', ContractEventParamSpecV0: { name: 'Text', @@ -152,18 +165,23 @@ const ContractMetadataV2 = { spec: 'ContractContractSpecV2' }; +const ContractMetadataV3 = { + types: 'Vec', + spec: 'ContractContractSpecV3' +}; + const ContractProjectInfo = { source: 'ContractProjectSource', contract: 'ContractProjectContract' }; const latest = { - ContractConstructorSpecLatest: 'ContractConstructorSpecV2', + ContractConstructorSpecLatest: 'ContractConstructorSpecV3', ContractEventSpecLatest: 'ContractEventSpecV2', ContractEventParamSpecLatest: 'ContractEventParamSpecV2', ContractMessageParamSpecLatest: 'ContractMessageParamSpecV2', ContractMessageSpecLatest: 'ContractMessageSpecV2', - ContractMetadataLatest: 'ContractMetadataV2' + ContractMetadataLatest: 'ContractMetadataV3' }; export default { @@ -173,11 +191,13 @@ export default { ContractMetadataV0, ContractMetadataV1, ContractMetadataV2, + ContractMetadataV3, ContractMetadata: { _enum: { V0: 'ContractMetadataV0', V1: 'ContractMetadataV1', - V2: 'ContractMetadataV2' + V2: 'ContractMetadataV2', + V3: 'ContractMetadataV3' } }, ContractProjectV0: objectSpread({ metadataVersion: 'Text' }, ContractProjectInfo, ContractMetadataV0), diff --git a/packages/types/src/interfaces/contractsAbi/types.ts b/packages/types/src/interfaces/contractsAbi/types.ts index 5927eafe0a..a166074d10 100644 --- a/packages/types/src/interfaces/contractsAbi/types.ts +++ b/packages/types/src/interfaces/contractsAbi/types.ts @@ -7,7 +7,7 @@ import type { PortableType } from '@polkadot/types/interfaces/metadata'; import type { Si0Type, SiLookupTypeId, SiPath } from '@polkadot/types/interfaces/scaleInfo'; /** @name ContractConstructorSpecLatest */ -export interface ContractConstructorSpecLatest extends ContractConstructorSpecV2 {} +export interface ContractConstructorSpecLatest extends ContractConstructorSpecV3 {} /** @name ContractConstructorSpecV0 */ export interface ContractConstructorSpecV0 extends Struct { @@ -25,6 +25,15 @@ export interface ContractConstructorSpecV2 extends Struct { readonly docs: Vec; } +/** @name ContractConstructorSpecV3 */ +export interface ContractConstructorSpecV3 extends Struct { + readonly label: Text; + readonly selector: ContractSelector; + readonly payable: bool; + readonly args: Vec; + readonly docs: Vec; +} + /** @name ContractContractSpecV0 */ export interface ContractContractSpecV0 extends Struct { readonly constructors: Vec; @@ -41,6 +50,14 @@ export interface ContractContractSpecV2 extends Struct { readonly docs: Vec; } +/** @name ContractContractSpecV3 */ +export interface ContractContractSpecV3 extends Struct { + readonly constructors: Vec; + readonly messages: Vec; + readonly events: Vec; + readonly docs: Vec; +} + /** @name ContractCryptoHasher */ export interface ContractCryptoHasher extends Enum { readonly isBlake2x256: boolean; @@ -187,11 +204,13 @@ export interface ContractMetadata extends Enum { readonly asV1: ContractMetadataV1; readonly isV2: boolean; readonly asV2: ContractMetadataV2; - readonly type: 'V0' | 'V1' | 'V2'; + readonly isV3: boolean; + readonly asV3: ContractMetadataV3; + readonly type: 'V0' | 'V1' | 'V2' | 'V3'; } /** @name ContractMetadataLatest */ -export interface ContractMetadataLatest extends ContractMetadataV2 {} +export interface ContractMetadataLatest extends ContractMetadataV3 {} /** @name ContractMetadataV0 */ export interface ContractMetadataV0 extends Struct { @@ -211,6 +230,12 @@ export interface ContractMetadataV2 extends Struct { readonly spec: ContractContractSpecV2; } +/** @name ContractMetadataV3 */ +export interface ContractMetadataV3 extends Struct { + readonly types: Vec; + readonly spec: ContractContractSpecV3; +} + /** @name ContractProject */ export interface ContractProject extends ITuple<[ContractProjectInfo, ContractMetadata]> {}