Adjust ABI registry (#3516)
* Adjust ABI registry * Remove V2 support * instantiateWithCode mock
This commit is contained in:
@@ -15,6 +15,8 @@ Changes:
|
||||
- Handle contract call RPC result directly as `Raw` (returned as hex)
|
||||
- Use `BN_*` constants as non-specified defaults in contract interfaces
|
||||
- Remove all type overrides for node/node-template (API tracks latest)
|
||||
- Contracts ABI now supports metadata version where indexes are 0-based
|
||||
- Remove support for the old-style V2 contracts from `api-contract`
|
||||
|
||||
|
||||
## 4.9.1 May 6, 2021
|
||||
|
||||
@@ -10,8 +10,6 @@ import { assert, assertReturn, compactAddLength, compactStripLength, isNumber, i
|
||||
|
||||
import { MetaRegistry } from './MetaRegistry';
|
||||
|
||||
const EMPTY_U8A = new Uint8Array();
|
||||
|
||||
const l = logger('Abi');
|
||||
|
||||
function findMessage <T extends AbiMessage> (list: T[], messageOrId: T | string | number): T {
|
||||
@@ -156,8 +154,8 @@ export class Abi {
|
||||
index,
|
||||
method: stringCamelCase(identifier),
|
||||
selector: spec.selector,
|
||||
toU8a: (params: CodecArg[], additional?: Uint8Array) =>
|
||||
this.#encodeArgs(spec, args, params, additional)
|
||||
toU8a: (params: CodecArg[]) =>
|
||||
this.#encodeArgs(spec, args, params)
|
||||
};
|
||||
|
||||
return message;
|
||||
@@ -187,14 +185,13 @@ export class Abi {
|
||||
return message.fromU8a(trimmed.subarray(4));
|
||||
}
|
||||
|
||||
#encodeArgs = ({ name, selector }: ContractMessageSpec | ContractConstructorSpec, args: AbiParam[], data: CodecArg[], additional = EMPTY_U8A): Uint8Array => {
|
||||
#encodeArgs = ({ name, selector }: ContractMessageSpec | ContractConstructorSpec, args: AbiParam[], data: CodecArg[]): Uint8Array => {
|
||||
assert(data.length === args.length, () => `Expected ${args.length} arguments to contract message '${name.toString()}', found ${data.length}`);
|
||||
|
||||
return compactAddLength(
|
||||
u8aConcat(
|
||||
this.registry.createType('ContractSelector', selector).toU8a(),
|
||||
...args.map(({ type }, index) => this.registry.createType(type.type as 'Text', data[index]).toU8a()),
|
||||
additional
|
||||
...args.map(({ type }, index) => this.registry.createType(type.type as 'Text', data[index]).toU8a())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,10 +35,8 @@ export class MetaRegistry extends TypeRegistry {
|
||||
constructor (metadataVersion: string, chainProperties?: ChainProperties) {
|
||||
super();
|
||||
|
||||
const [major] = metadataVersion.split('.');
|
||||
|
||||
// type indexes are 1-based pre-1.0 and 0-based post-1.0
|
||||
this.typeOffset = major === '0' ? 1 : 0;
|
||||
this.typeOffset = metadataVersion.split('.')[0] === '0' ? 1 : 0;
|
||||
|
||||
if (chainProperties) {
|
||||
this.setChainProperties(chainProperties);
|
||||
@@ -94,10 +92,13 @@ export class MetaRegistry extends TypeRegistry {
|
||||
|
||||
#extract = (type: SiType, id: SiLookupTypeId): TypeDef => {
|
||||
const path = [...type.path];
|
||||
const pathFinal = path.length ? path[path.length - 1].toString() : '';
|
||||
const isPrimitivePath = !!path.length && (
|
||||
(path.length > 2 && path[0].eq('ink_env') && path[1].eq('types')) ||
|
||||
PRIMITIVE_ALWAYS.includes(path[path.length - 1].toString())
|
||||
);
|
||||
let typeDef: Omit<TypeDef, 'type'>;
|
||||
|
||||
if (type.path.join('::').startsWith('ink_env::types::') || PRIMITIVE_ALWAYS.includes(pathFinal)) {
|
||||
if (isPrimitivePath) {
|
||||
typeDef = this.#extractPrimitivePath(type);
|
||||
} else if (type.def.isPrimitive) {
|
||||
typeDef = this.#extractPrimitive(type);
|
||||
@@ -135,7 +136,7 @@ export class MetaRegistry extends TypeRegistry {
|
||||
}
|
||||
|
||||
#extractArray = ({ len: length, type }: SiTypeDefArray): Omit<TypeDef, 'type'> => {
|
||||
assert(!length || length.toNumber() <= 256, 'MetaRegistry: Only support for [Type; <length>], where length > 256');
|
||||
assert(!length || length.toNumber() <= 256, 'MetaRegistry: Only support for [Type; <length>], where length <= 256');
|
||||
|
||||
return {
|
||||
info: TypeDefInfo.VecFixed,
|
||||
@@ -151,27 +152,27 @@ export class MetaRegistry extends TypeRegistry {
|
||||
]),
|
||||
[true, true]);
|
||||
|
||||
let info;
|
||||
|
||||
// check for tuple first (no fields may be available)
|
||||
if (isTuple) {
|
||||
info = TypeDefInfo.Tuple;
|
||||
} else if (isStruct) {
|
||||
info = TypeDefInfo.Struct;
|
||||
} else {
|
||||
throw new Error('Invalid fields type detected, expected either Tuple or Struct');
|
||||
}
|
||||
assert(isTuple || isStruct, 'Invalid fields type detected, expected either Tuple or Struct');
|
||||
|
||||
const sub = fields.map(({ name, type }) => {
|
||||
return {
|
||||
...this.getMetaTypeDef({ type }),
|
||||
...(name.isSome ? { name: name.unwrap().toString() } : {})
|
||||
...(name.isSome
|
||||
? { name: name.unwrap().toString() }
|
||||
: {}
|
||||
)
|
||||
};
|
||||
});
|
||||
|
||||
return isTuple && sub.length === 1
|
||||
? sub[0]
|
||||
: { info, sub };
|
||||
: {
|
||||
// check for tuple first (no fields may be available)
|
||||
info: isTuple
|
||||
? TypeDefInfo.Tuple
|
||||
: TypeDefInfo.Struct,
|
||||
sub
|
||||
};
|
||||
}
|
||||
|
||||
#extractPrimitive = (type: SiType): TypeDef => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { ApiTypes, DecorateMethod } from '@polkadot/api/types';
|
||||
import type { AnyJson, Registry } from '@polkadot/types/types';
|
||||
|
||||
import { ApiBase } from '@polkadot/api/base';
|
||||
import { assert } from '@polkadot/util';
|
||||
import { assert, isFunction } from '@polkadot/util';
|
||||
|
||||
import { Abi } from '../Abi';
|
||||
|
||||
@@ -26,6 +26,6 @@ export abstract class Base<ApiType extends ApiTypes> {
|
||||
this.registry = this.abi.registry;
|
||||
this._decorateMethod = decorateMethod;
|
||||
|
||||
assert(api.rx.tx.contracts?.instantiate, 'You need to connect to a node with the contracts module, the metadata does not enable api.tx.contracts on this instance');
|
||||
assert(isFunction(api.rx.tx.contracts?.instantiateWithCode), 'You need to connect to a node with a V3 contracts module. Your node does not expose api.tx.contracts.instantiateWithCode on this instance');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Abi } from '../Abi';
|
||||
import { applyOnEvent } from '../util';
|
||||
import { Base } from './Base';
|
||||
import { Contract } from './Contract';
|
||||
import { createBluePrintTx, EMPTY_SALT, encodeSalt } from './util';
|
||||
import { createBluePrintTx, encodeSalt } from './util';
|
||||
|
||||
export class BlueprintSubmittableResult<ApiType extends ApiTypes> extends SubmittableResult {
|
||||
public readonly contract?: Contract<ApiType>;
|
||||
@@ -53,19 +53,18 @@ export class Blueprint<ApiType extends ApiTypes> extends Base<ApiType> {
|
||||
}
|
||||
|
||||
#deploy = (constructorOrId: AbiConstructor | string | number, { gasLimit = BN_ZERO, salt, value = BN_ZERO }: BlueprintOptions, params: CodecArg[]): SubmittableExtrinsic<ApiType, BlueprintSubmittableResult<ApiType>> => {
|
||||
const encodedSalt = encodeSalt(salt);
|
||||
const withSalt = this.api.tx.contracts.instantiate.meta.args.length === 5;
|
||||
const encoded = this.abi.findConstructor(constructorOrId).toU8a(params, withSalt ? EMPTY_SALT : encodedSalt);
|
||||
const tx = withSalt
|
||||
? this.api.tx.contracts.instantiate(value, gasLimit, this.codeHash, encoded, encodedSalt)
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore old style with salt included
|
||||
: this.api.tx.contracts.instantiate(value, gasLimit, this.codeHash, encoded);
|
||||
|
||||
return tx.withResultTransform((result: ISubmittableResult) =>
|
||||
new BlueprintSubmittableResult(result, applyOnEvent(result, ['Instantiated'], ([record]: EventRecord[]) =>
|
||||
new Contract<ApiType>(this.api, this.abi, record.event.data[1] as AccountId, this._decorateMethod)
|
||||
))
|
||||
);
|
||||
return this.api.tx.contracts
|
||||
.instantiate(
|
||||
value,
|
||||
gasLimit,
|
||||
this.codeHash,
|
||||
this.abi.findConstructor(constructorOrId).toU8a(params),
|
||||
encodeSalt(salt)
|
||||
)
|
||||
.withResultTransform((result: ISubmittableResult) =>
|
||||
new BlueprintSubmittableResult(result, applyOnEvent(result, ['Instantiated'], ([record]: EventRecord[]) =>
|
||||
new Contract<ApiType>(this.api, this.abi, record.event.data[1] as AccountId, this._decorateMethod)
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,14 @@ import type { MapConstructorExec } from './types';
|
||||
|
||||
import { SubmittableResult } from '@polkadot/api';
|
||||
import { ApiBase } from '@polkadot/api/base';
|
||||
import { assert, BN_ZERO, compactAddLength, isFunction, isUndefined, isWasm, u8aToU8a } from '@polkadot/util';
|
||||
import { blake2AsU8a } from '@polkadot/util-crypto';
|
||||
import { assert, BN_ZERO, compactAddLength, isUndefined, isWasm, u8aToU8a } from '@polkadot/util';
|
||||
|
||||
import { Abi } from '../Abi';
|
||||
import { applyOnEvent } from '../util';
|
||||
import { Base } from './Base';
|
||||
import { Blueprint } from './Blueprint';
|
||||
import { Contract } from './Contract';
|
||||
import { createBluePrintTx, EMPTY_SALT, encodeSalt } from './util';
|
||||
import { createBluePrintTx, encodeSalt } from './util';
|
||||
|
||||
export class CodeSubmittableResult<ApiType extends ApiTypes> extends SubmittableResult {
|
||||
public readonly blueprint?: Blueprint<ApiType>;
|
||||
@@ -57,45 +56,25 @@ export class Code<ApiType extends ApiTypes> extends Base<ApiType> {
|
||||
return this.#tx;
|
||||
}
|
||||
|
||||
#instantiate = (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: CodecArg[]): SubmittableExtrinsic<ApiType, CodeSubmittableResult<ApiType>> => {
|
||||
return isFunction(this.api.tx.contracts.instantiateWithCode)
|
||||
? this.#instantiateCurrent(constructorOrId, options, params)
|
||||
: this.#instantiatePrev(constructorOrId, options, params);
|
||||
}
|
||||
|
||||
#instantiateCurrent = (constructorOrId: AbiConstructor | string | number, { gasLimit = BN_ZERO, salt, value = BN_ZERO }: BlueprintOptions, params: CodecArg[]): SubmittableExtrinsic<ApiType, CodeSubmittableResult<ApiType>> => {
|
||||
#instantiate = (constructorOrId: AbiConstructor | string | number, { gasLimit = BN_ZERO, salt, value = BN_ZERO }: BlueprintOptions, params: CodecArg[]): SubmittableExtrinsic<ApiType, CodeSubmittableResult<ApiType>> => {
|
||||
return this.api.tx.contracts
|
||||
.instantiateWithCode(value, gasLimit, compactAddLength(this.code), this.abi.findConstructor(constructorOrId).toU8a(params), encodeSalt(salt))
|
||||
.withResultTransform(this.#transformEvents);
|
||||
}
|
||||
|
||||
#instantiatePrev = (constructorOrId: AbiConstructor | string | number, { gasLimit = BN_ZERO, salt, value = BN_ZERO }: BlueprintOptions, params: CodecArg[]): SubmittableExtrinsic<ApiType, CodeSubmittableResult<ApiType>> => {
|
||||
assert(isFunction(this.api.tx.utility?.batch), 'Your chain does not include the utility pallet, for contracts v2 deployment, this is required');
|
||||
|
||||
const encodedSalt = encodeSalt(salt);
|
||||
const withSalt = this.api.tx.contracts.instantiate.meta.args.length === 5;
|
||||
const encoded = this.abi.findConstructor(constructorOrId).toU8a(params, withSalt ? EMPTY_SALT : encodedSalt);
|
||||
const codeHash = blake2AsU8a(this.code);
|
||||
|
||||
return this.api.tx.utility.batch([
|
||||
this.api.tx.contracts.putCode(compactAddLength(this.code)),
|
||||
withSalt
|
||||
? this.api.tx.contracts.instantiate(value, gasLimit, codeHash, encoded, encodedSalt)
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore old style with salt included in the params itself
|
||||
: this.api.tx.contracts.instantiate(value, gasLimit, codeHash, encoded)
|
||||
]).withResultTransform(this.#transformEvents);
|
||||
}
|
||||
|
||||
#transformEvents = (result: ISubmittableResult): CodeSubmittableResult<ApiType> => {
|
||||
return new CodeSubmittableResult(result, ...(applyOnEvent(result, ['CodeStored', 'Instantiated'], (records: EventRecord[]) =>
|
||||
records.reduce(([blueprint, contract], { event }): [Blueprint<ApiType>?, Contract<ApiType>?] =>
|
||||
this.api.events.contracts.Instantiated.is(event)
|
||||
? [blueprint, new Contract<ApiType>(this.api, this.abi, event.data[1], this._decorateMethod)]
|
||||
: this.api.events.contracts.CodeStored.is(event)
|
||||
? [new Blueprint<ApiType>(this.api, this.abi, event.data[0], this._decorateMethod), contract]
|
||||
: [blueprint, contract],
|
||||
[] as [Blueprint<ApiType>?, Contract<ApiType>?])
|
||||
) || []));
|
||||
.instantiateWithCode(
|
||||
value,
|
||||
gasLimit,
|
||||
compactAddLength(this.code),
|
||||
this.abi.findConstructor(constructorOrId).toU8a(params),
|
||||
encodeSalt(salt)
|
||||
)
|
||||
.withResultTransform((result: ISubmittableResult) =>
|
||||
new CodeSubmittableResult(result, ...(applyOnEvent(result, ['CodeStored', 'Instantiated'], (records: EventRecord[]) =>
|
||||
records.reduce<[Blueprint<ApiType>?, Contract<ApiType>?]>(([blueprint, contract], { event }) =>
|
||||
this.api.events.contracts.Instantiated.is(event)
|
||||
? [blueprint, new Contract<ApiType>(this.api, this.abi, event.data[1], this._decorateMethod)]
|
||||
: this.api.events.contracts.CodeStored.is(event)
|
||||
? [new Blueprint<ApiType>(this.api, this.abi, event.data[0], this._decorateMethod), contract]
|
||||
: [blueprint, contract],
|
||||
[])
|
||||
) || []))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,12 @@ export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
|
||||
|
||||
#exec = (messageOrId: AbiMessage | string | number, { gasLimit = BN_ZERO, value = BN_ZERO }: ContractOptions, params: CodecArg[]): SubmittableExtrinsic<ApiType> => {
|
||||
return this.api.tx.contracts
|
||||
.call(this.address, value, this.#getGas(gasLimit), this.abi.findMessage(messageOrId).toU8a(params))
|
||||
.call(
|
||||
this.address,
|
||||
value,
|
||||
this.#getGas(gasLimit),
|
||||
this.abi.findMessage(messageOrId).toU8a(params)
|
||||
)
|
||||
.withResultTransform((result: ISubmittableResult) =>
|
||||
// ContractEmitted is the current generation, ContractExecution is the previous generation
|
||||
new ContractSubmittableResult(result, applyOnEvent(result, ['ContractEmitted', 'ContractExecution'], (records: EventRecord[]) =>
|
||||
|
||||
@@ -11,10 +11,7 @@ export const mockApi = {
|
||||
rx: {
|
||||
tx: {
|
||||
contracts: {
|
||||
instantiate: (): never => {
|
||||
throw new Error('mock');
|
||||
},
|
||||
putCode: (): never => {
|
||||
instantiateWithCode: (): never => {
|
||||
throw new Error('mock');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export interface AbiMessage {
|
||||
method: string;
|
||||
returnType?: TypeDef | null;
|
||||
selector: ContractSelector;
|
||||
toU8a: (params: CodecArg[], additional?: Uint8Array) => Uint8Array;
|
||||
toU8a: (params: CodecArg[]) => Uint8Array;
|
||||
}
|
||||
|
||||
export type AbiConstructor = AbiMessage;
|
||||
|
||||
Reference in New Issue
Block a user