Skip to content

Commit

Permalink
Support for ink! metadata v3 (#4432)
Browse files Browse the repository at this point in the history
* Support for ink! metadata v3

* Flatten check for V1-V3

* DRY-er is/as conversions

* Test for v2 conversion

* Remove stray console.log

* Generic converter

* Cleanups
  • Loading branch information
jacogr authored Jan 18, 2022
1 parent 91989e1 commit dfbfaf1
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 28 deletions.
26 changes: 11 additions & 15 deletions packages/api-contract/src/Abi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -26,23 +26,18 @@ function findMessage <T extends AbiMessage> (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<string, unknown>): 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<string, unknown>, chainProperties?: ChainProperties): [Record<string, unknown>, Registry, ContractMetadataLatest, ContractProjectInfo] {
Expand Down Expand Up @@ -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) =>
Expand Down
14 changes: 13 additions & 1 deletion packages/api-contract/src/Abi/toLatest.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
});
});
32 changes: 27 additions & 5 deletions packages/api-contract/src/Abi/toLatest.ts
Original file line number Diff line number Diff line change
@@ -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 <I, O> (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]
];
18 changes: 18 additions & 0 deletions packages/api-contract/src/Abi/toV3.ts
Original file line number Diff line number Diff line change
@@ -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 <V3, it is always true
registry.createType('ContractConstructorSpecV3', objectSpread({}, c, { payable: true }))
)
})
}));
}
5 changes: 4 additions & 1 deletion packages/types-augment/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { StatementKind } from '@polkadot/types/interfaces/claims';
import type { CollectiveOrigin, MemberCount, ProposalIndex, Votes, VotesTo230 } from '@polkadot/types/interfaces/collective';
import type { AuthorityId, RawVRFOutput } from '@polkadot/types/interfaces/consensus';
import type { AliveContractInfo, CodeHash, ContractCallFlags, ContractCallRequest, ContractExecResult, ContractExecResultErr, ContractExecResultErrModule, ContractExecResultOk, ContractExecResultResult, ContractExecResultSuccessTo255, ContractExecResultSuccessTo260, ContractExecResultTo255, ContractExecResultTo260, ContractExecResultTo267, ContractInfo, ContractInstantiateResult, ContractInstantiateResultTo267, ContractReturnFlags, ContractStorageKey, DeletedContract, ExecReturnValue, Gas, HostFnWeights, HostFnWeightsTo264, InstantiateRequest, InstantiateReturnValue, InstantiateReturnValueTo267, InstructionWeights, Limits, LimitsTo264, PrefabWasmModule, RentProjection, Schedule, ScheduleTo212, ScheduleTo258, ScheduleTo264, SeedOf, StorageDeposit, TombstoneContractInfo, TrieId } from '@polkadot/types/interfaces/contracts';
import type { ContractConstructorSpecLatest, ContractConstructorSpecV0, ContractConstructorSpecV2, ContractContractSpecV0, ContractContractSpecV2, ContractCryptoHasher, ContractDiscriminant, ContractDisplayName, ContractEventParamSpecLatest, ContractEventParamSpecV0, ContractEventParamSpecV2, ContractEventSpecLatest, ContractEventSpecV0, ContractEventSpecV2, ContractLayoutArray, ContractLayoutCell, ContractLayoutEnum, ContractLayoutHash, ContractLayoutHashingStrategy, ContractLayoutKey, ContractLayoutStruct, ContractLayoutStructField, ContractMessageParamSpecLatest, ContractMessageParamSpecV0, ContractMessageParamSpecV2, ContractMessageSpecLatest, ContractMessageSpecV0, ContractMessageSpecV2, ContractMetadata, ContractMetadataLatest, ContractMetadataV0, ContractMetadataV1, ContractMetadataV2, ContractProject, ContractProjectContract, ContractProjectInfo, ContractProjectSource, ContractProjectV0, ContractSelector, ContractStorageLayout, ContractTypeSpec } from '@polkadot/types/interfaces/contractsAbi';
import type { ContractConstructorSpecLatest, ContractConstructorSpecV0, ContractConstructorSpecV2, ContractConstructorSpecV3, ContractContractSpecV0, ContractContractSpecV2, ContractContractSpecV3, ContractCryptoHasher, ContractDiscriminant, ContractDisplayName, ContractEventParamSpecLatest, ContractEventParamSpecV0, ContractEventParamSpecV2, ContractEventSpecLatest, ContractEventSpecV0, ContractEventSpecV2, ContractLayoutArray, ContractLayoutCell, ContractLayoutEnum, ContractLayoutHash, ContractLayoutHashingStrategy, ContractLayoutKey, ContractLayoutStruct, ContractLayoutStructField, ContractMessageParamSpecLatest, ContractMessageParamSpecV0, ContractMessageParamSpecV2, ContractMessageSpecLatest, ContractMessageSpecV0, ContractMessageSpecV2, ContractMetadata, ContractMetadataLatest, ContractMetadataV0, ContractMetadataV1, ContractMetadataV2, ContractMetadataV3, ContractProject, ContractProjectContract, ContractProjectInfo, ContractProjectSource, ContractProjectV0, ContractSelector, ContractStorageLayout, ContractTypeSpec } from '@polkadot/types/interfaces/contractsAbi';
import type { FundIndex, FundInfo, LastContribution, TrieIndex } from '@polkadot/types/interfaces/crowdloan';
import type { ConfigData, MessageId, OverweightIndex, PageCounter, PageIndexData } from '@polkadot/types/interfaces/cumulus';
import type { AccountVote, AccountVoteSplit, AccountVoteStandard, Conviction, Delegations, PreimageStatus, PreimageStatusAvailable, PriorLock, PropIndex, Proposal, ProxyState, ReferendumIndex, ReferendumInfo, ReferendumInfoFinished, ReferendumInfoTo239, ReferendumStatus, Tally, Voting, VotingDelegating, VotingDirect, VotingDirectVote } from '@polkadot/types/interfaces/democracy';
Expand Down Expand Up @@ -217,8 +217,10 @@ declare module '@polkadot/types/types/registry' {
ContractConstructorSpecLatest: ContractConstructorSpecLatest;
ContractConstructorSpecV0: ContractConstructorSpecV0;
ContractConstructorSpecV2: ContractConstructorSpecV2;
ContractConstructorSpecV3: ContractConstructorSpecV3;
ContractContractSpecV0: ContractContractSpecV0;
ContractContractSpecV2: ContractContractSpecV2;
ContractContractSpecV3: ContractContractSpecV3;
ContractCryptoHasher: ContractCryptoHasher;
ContractDiscriminant: ContractDiscriminant;
ContractDisplayName: ContractDisplayName;
Expand Down Expand Up @@ -260,6 +262,7 @@ declare module '@polkadot/types/types/registry' {
ContractMetadataV0: ContractMetadataV0;
ContractMetadataV1: ContractMetadataV1;
ContractMetadataV2: ContractMetadataV2;
ContractMetadataV3: ContractMetadataV3;
ContractProject: ContractProject;
ContractProjectContract: ContractProjectContract;
ContractProjectInfo: ContractProjectInfo;
Expand Down
26 changes: 23 additions & 3 deletions packages/types/src/interfaces/contractsAbi/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ const spec = {
args: 'Vec<ContractMessageParamSpecV2>',
docs: 'Vec<Text>'
},
ContractConstructorSpecV3: {
label: 'Text',
selector: 'ContractSelector',
payable: 'bool',
args: 'Vec<ContractMessageParamSpecV2>',
docs: 'Vec<Text>'
},
ContractContractSpecV0: {
constructors: 'Vec<ContractConstructorSpecV0>',
messages: 'Vec<ContractMessageSpecV0>',
Expand All @@ -81,6 +88,12 @@ const spec = {
events: 'Vec<ContractEventSpecV2>',
docs: 'Vec<Text>'
},
ContractContractSpecV3: {
constructors: 'Vec<ContractConstructorSpecV3>',
messages: 'Vec<ContractMessageSpecV2>',
events: 'Vec<ContractEventSpecV2>',
docs: 'Vec<Text>'
},
ContractDisplayName: 'SiPath',
ContractEventParamSpecV0: {
name: 'Text',
Expand Down Expand Up @@ -152,18 +165,23 @@ const ContractMetadataV2 = {
spec: 'ContractContractSpecV2'
};

const ContractMetadataV3 = {
types: 'Vec<PortableType>',
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 {
Expand All @@ -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),
Expand Down
31 changes: 28 additions & 3 deletions packages/types/src/interfaces/contractsAbi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -25,6 +25,15 @@ export interface ContractConstructorSpecV2 extends Struct {
readonly docs: Vec<Text>;
}

/** @name ContractConstructorSpecV3 */
export interface ContractConstructorSpecV3 extends Struct {
readonly label: Text;
readonly selector: ContractSelector;
readonly payable: bool;
readonly args: Vec<ContractMessageParamSpecV2>;
readonly docs: Vec<Text>;
}

/** @name ContractContractSpecV0 */
export interface ContractContractSpecV0 extends Struct {
readonly constructors: Vec<ContractConstructorSpecV0>;
Expand All @@ -41,6 +50,14 @@ export interface ContractContractSpecV2 extends Struct {
readonly docs: Vec<Text>;
}

/** @name ContractContractSpecV3 */
export interface ContractContractSpecV3 extends Struct {
readonly constructors: Vec<ContractConstructorSpecV3>;
readonly messages: Vec<ContractMessageSpecV2>;
readonly events: Vec<ContractEventSpecV2>;
readonly docs: Vec<Text>;
}

/** @name ContractCryptoHasher */
export interface ContractCryptoHasher extends Enum {
readonly isBlake2x256: boolean;
Expand Down Expand Up @@ -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 {
Expand All @@ -211,6 +230,12 @@ export interface ContractMetadataV2 extends Struct {
readonly spec: ContractContractSpecV2;
}

/** @name ContractMetadataV3 */
export interface ContractMetadataV3 extends Struct {
readonly types: Vec<PortableType>;
readonly spec: ContractContractSpecV3;
}

/** @name ContractProject */
export interface ContractProject extends ITuple<[ContractProjectInfo, ContractMetadata]> {}

Expand Down

0 comments on commit dfbfaf1

Please sign in to comment.