diff --git a/CHANGELOG.md b/CHANGELOG.md index 30515a921c..f523e5c44a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ Changes: - Adjust handling of `Weight` type for V2 structures -- Cater for weight v2 (with old pass-through) extrinsics in `api-contract` +- Cater for weight v2 (along with v1 pass-through) extrinsics in `api-contract` - Update to latest Substrate, Polkadot & Kusama metadata diff --git a/packages/api-contract/src/base/Base.ts b/packages/api-contract/src/base/Base.ts index 0f9894a7fb..a0a399581e 100644 --- a/packages/api-contract/src/base/Base.ts +++ b/packages/api-contract/src/base/Base.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { ApiTypes, DecorateMethod } from '@polkadot/api/types'; -import type { Registry } from '@polkadot/types/types'; +import type { INumber, Registry } from '@polkadot/types/types'; import { ApiBase } from '@polkadot/api/base'; import { isFunction } from '@polkadot/util'; @@ -16,14 +16,9 @@ export abstract class Base { protected readonly _decorateMethod: DecorateMethod; + protected readonly _isOldWeight: boolean; + constructor (api: ApiBase, abi: string | Record | Abi, decorateMethod: DecorateMethod) { - this.abi = abi instanceof Abi - ? abi - : new Abi(abi, api.registry.getChainProperties()); - this.api = api; - - this._decorateMethod = decorateMethod; - if (!api || !api.isConnected || !api.tx) { throw new Error('Your API has not been initialized correctly and is not connected to a chain'); } else if (!api.tx.contracts || !isFunction(api.tx.contracts.instantiateWithCode) || api.tx.contracts.instantiateWithCode.meta.args.length !== 6) { @@ -31,6 +26,13 @@ export abstract class Base { } else if (!api.call.contractsApi || !isFunction(api.call.contractsApi.call)) { throw new Error('Your runtime does not expose the api.call.contractsApi.call runtime interfaces'); } + + this.abi = abi instanceof Abi + ? abi + : new Abi(abi, api.registry.getChainProperties()); + this.api = api; + this._decorateMethod = decorateMethod; + this._isOldWeight = isFunction(api.registry.createType('Weight').toBn); } public get registry (): Registry { diff --git a/packages/api-contract/src/base/Blueprint.ts b/packages/api-contract/src/base/Blueprint.ts index 6fca7eae52..08c6962c6c 100644 --- a/packages/api-contract/src/base/Blueprint.ts +++ b/packages/api-contract/src/base/Blueprint.ts @@ -5,7 +5,7 @@ import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; import type { ApiTypes, DecorateMethod } from '@polkadot/api/types'; import type { AccountId, EventRecord, Hash } from '@polkadot/types/interfaces'; import type { ISubmittableResult } from '@polkadot/types/types'; -import type { AbiConstructor, BlueprintOptions } from '../types'; +import type { AbiConstructor, BlueprintOptions, WeightAll } from '../types'; import type { MapConstructorExec } from './types'; import { SubmittableResult } from '@polkadot/api'; @@ -16,7 +16,7 @@ import { Abi } from '../Abi'; import { applyOnEvent } from '../util'; import { Base } from './Base'; import { Contract } from './Contract'; -import { createBluePrintTx, encodeSalt } from './util'; +import { convertWeight, createBluePrintTx, encodeSalt } from './util'; export interface BlueprintConstructor { new(api: ApiBase, abi: string | Record | Abi, codeHash: string | Hash | Uint8Array): Blueprint; @@ -57,13 +57,12 @@ export class Blueprint extends Base { } #deploy = (constructorOrId: AbiConstructor | string | number, { gasLimit = BN_ZERO, salt, storageDepositLimit = null, value = BN_ZERO }: BlueprintOptions, params: unknown[]): SubmittableExtrinsic> => { - // TODO Cater for new call structure (with old fallback) - return ( - this.api.tx.contracts.instantiateOldWeight || - this.api.tx.contracts.instantiate - )( + return this.api.tx.contracts.instantiate( value, - gasLimit, + this._isOldWeight + // jiggle v1 weights, metadata points to latest + ? convertWeight(gasLimit).v1Weight as unknown as WeightAll['v2Weight'] + : convertWeight(gasLimit).v2Weight, storageDepositLimit, this.codeHash, this.abi.findConstructor(constructorOrId).toU8a(params), diff --git a/packages/api-contract/src/base/Code.ts b/packages/api-contract/src/base/Code.ts index f46cf6a2fe..f277c497f6 100644 --- a/packages/api-contract/src/base/Code.ts +++ b/packages/api-contract/src/base/Code.ts @@ -6,7 +6,7 @@ import type { ApiTypes, DecorateMethod } from '@polkadot/api/types'; import type { AccountId, EventRecord } from '@polkadot/types/interfaces'; import type { ISubmittableResult } from '@polkadot/types/types'; import type { Codec } from '@polkadot/types-codec/types'; -import type { AbiConstructor, BlueprintOptions } from '../types'; +import type { AbiConstructor, BlueprintOptions, WeightAll } from '../types'; import type { MapConstructorExec } from './types'; import { SubmittableResult } from '@polkadot/api'; @@ -18,7 +18,7 @@ import { applyOnEvent } from '../util'; import { Base } from './Base'; import { Blueprint } from './Blueprint'; import { Contract } from './Contract'; -import { createBluePrintTx, encodeSalt } from './util'; +import { convertWeight, createBluePrintTx, encodeSalt } from './util'; export interface CodeConstructor { new(api: ApiBase, abi: string | Record | Abi, wasm: Uint8Array | string | Buffer | null | undefined): Code; @@ -64,13 +64,12 @@ export class Code extends Base { } #instantiate = (constructorOrId: AbiConstructor | string | number, { gasLimit = BN_ZERO, salt, storageDepositLimit = null, value = BN_ZERO }: BlueprintOptions, params: unknown[]): SubmittableExtrinsic> => { - // TODO Cater for new call structure (with old fallback) - return ( - this.api.tx.contracts.instantiateWithCodeOldWeight || - this.api.tx.contracts.instantiateWithCode - )( + return this.api.tx.contracts.instantiateWithCode( value, - gasLimit, + this._isOldWeight + // jiggle v1 weights, metadata points to latest + ? convertWeight(gasLimit).v1Weight as unknown as WeightAll['v2Weight'] + : convertWeight(gasLimit).v2Weight, storageDepositLimit, compactAddLength(this.code), this.abi.findConstructor(constructorOrId).toU8a(params), diff --git a/packages/api-contract/src/base/Contract.ts b/packages/api-contract/src/base/Contract.ts index aa590529f7..89f50b1c6b 100644 --- a/packages/api-contract/src/base/Contract.ts +++ b/packages/api-contract/src/base/Contract.ts @@ -3,22 +3,22 @@ import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; import type { ApiTypes, DecorateMethod } from '@polkadot/api/types'; -import type { Bytes, Compact } from '@polkadot/types'; +import type { Bytes } from '@polkadot/types'; import type { AccountId, ContractExecResult, EventRecord, Weight } from '@polkadot/types/interfaces'; import type { ISubmittableResult } from '@polkadot/types/types'; -import type { AbiMessage, ContractCallOutcome, ContractOptions, DecodedEvent } from '../types'; +import type { AbiMessage, ContractCallOutcome, ContractOptions, DecodedEvent, WeightAll, WeightV2 } from '../types'; import type { ContractCallResult, ContractCallSend, ContractQuery, ContractTx, MapMessageQuery, MapMessageTx } from './types'; import { map } from 'rxjs'; import { SubmittableResult } from '@polkadot/api'; import { ApiBase } from '@polkadot/api/base'; -import { BN, BN_HUNDRED, BN_ONE, BN_ZERO, bnToBn, isUndefined, logger } from '@polkadot/util'; +import { BN, BN_HUNDRED, BN_ONE, BN_ZERO, isUndefined, logger } from '@polkadot/util'; import { Abi } from '../Abi'; import { applyOnEvent } from '../util'; import { Base } from './Base'; -import { withMeta } from './util'; +import { convertWeight, withMeta } from './util'; export interface ContractConstructor { new(api: ApiBase, abi: string | Record | Abi, address: string | AccountId): Contract; @@ -85,37 +85,32 @@ export class Contract extends Base { return this.#tx; } - #getMaxGas = (): BN => { - if (this.api.consts.system.blockWeights) { - const maxBlock = (this.api.consts.system.blockWeights as unknown as { maxBlock: { proofSize: Compact, refTime: Compact } }).maxBlock; + #getGas = (_gasLimit: bigint | BN | string | number | WeightV2, isCall = false): WeightAll => { + const weight = convertWeight(_gasLimit); - return maxBlock.proofSize - ? maxBlock.refTime.unwrap() - : maxBlock as unknown as Weight; + if (weight.v1Weight.gt(BN_ZERO)) { + return weight; } - return this.api.consts.system.maximumBlockWeight as Weight; - }; - - #getGas = (_gasLimit: bigint | BN | string | number, isCall = false): BN => { - const gasLimit = bnToBn(_gasLimit); - - return gasLimit.lte(BN_ZERO) - ? isCall + return convertWeight( + isCall ? MAX_CALL_GAS - : this.#getMaxGas().muln(64).div(BN_HUNDRED) - : gasLimit; + : convertWeight( + this.api.consts.system.blockWeights + ? (this.api.consts.system.blockWeights as unknown as { maxBlock: WeightV2 }).maxBlock + : this.api.consts.system.maximumBlockWeight as Weight + ).v1Weight.muln(64).div(BN_HUNDRED) + ); }; #exec = (messageOrId: AbiMessage | string | number, { gasLimit = BN_ZERO, storageDepositLimit = null, value = BN_ZERO }: ContractOptions, params: unknown[]): SubmittableExtrinsic => { - // TODO Cater for new call structure (with old fallback) - return ( - this.api.tx.contracts.callOldWeight || - this.api.tx.contracts.call - )( + return this.api.tx.contracts.call( this.address, value, - this.#getGas(gasLimit), + this._isOldWeight + // jiggle v1 weights, metadata points to latest + ? convertWeight(gasLimit).v1Weight as unknown as WeightAll['v2Weight'] + : convertWeight(gasLimit).v2Weight, storageDepositLimit, this.abi.findMessage(messageOrId).toU8a(params) ).withResultTransform((result: ISubmittableResult) => @@ -142,22 +137,28 @@ export class Contract extends Base { return { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment send: this._decorateMethod((origin: string | AccountId | Uint8Array) => - this.api.rx.call.contractsApi - .call(origin, this.address, value, this.#getGas(gasLimit, true), storageDepositLimit, message.toU8a(params)) - .pipe( - map(({ debugMessage, gasConsumed, gasRequired, result, storageDeposit }): ContractCallOutcome => ({ - debugMessage, - gasConsumed, - gasRequired: gasRequired && !gasRequired.isZero() - ? gasRequired - : gasConsumed, - output: result.isOk && message.returnType - ? this.abi.registry.createTypeUnsafe(message.returnType.lookupName || message.returnType.type, [result.asOk.data.toU8a(true)], { isPedantic: true }) - : null, - result, - storageDeposit - })) - ) + this.api.rx.call.contractsApi.call( + origin, + this.address, + value, + // the runtime interface still used u64 inputs + this.#getGas(gasLimit, true).v1Weight, + storageDepositLimit, + message.toU8a(params) + ).pipe( + map(({ debugMessage, gasConsumed, gasRequired, result, storageDeposit }): ContractCallOutcome => ({ + debugMessage, + gasConsumed, + gasRequired: gasRequired && !gasRequired.isZero() + ? gasRequired + : gasConsumed, + output: result.isOk && message.returnType + ? this.abi.registry.createTypeUnsafe(message.returnType.lookupName || message.returnType.type, [result.asOk.data.toU8a(true)], { isPedantic: true }) + : null, + result, + storageDeposit + })) + ) ) }; }; diff --git a/packages/api-contract/src/base/util.ts b/packages/api-contract/src/base/util.ts index 0c448f2690..55fcb6f5b2 100644 --- a/packages/api-contract/src/base/util.ts +++ b/packages/api-contract/src/base/util.ts @@ -4,11 +4,12 @@ import type { SubmittableResult } from '@polkadot/api'; import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; import type { ApiTypes } from '@polkadot/api/types'; -import type { AbiConstructor, AbiMessage, BlueprintOptions } from '../types'; +import type { BN } from '@polkadot/util'; +import type { AbiConstructor, AbiMessage, BlueprintOptions, WeightAll, WeightV2 } from '../types'; import type { BlueprintDeploy, ContractGeneric } from './types'; import { Bytes } from '@polkadot/types'; -import { compactAddLength, u8aToU8a } from '@polkadot/util'; +import { bnToBn, compactAddLength, u8aToU8a } from '@polkadot/util'; import { randomAsU8a } from '@polkadot/util-crypto'; export const EMPTY_SALT = new Uint8Array(); @@ -37,3 +38,14 @@ export function encodeSalt (salt: Uint8Array | string | null = randomAsU8a()): U ? compactAddLength(u8aToU8a(salt)) : EMPTY_SALT; } + +export function convertWeight (orig: WeightV2 | bigint | string | number | BN): WeightAll { + const refTime = (orig as WeightV2).proofSize + ? (orig as WeightV2).refTime.toBn() + : bnToBn(orig as BN); + + return { + v1Weight: refTime, + v2Weight: { refTime } + }; +} diff --git a/packages/api-contract/src/types.ts b/packages/api-contract/src/types.ts index 6a13492cc7..c5b859a41e 100644 --- a/packages/api-contract/src/types.ts +++ b/packages/api-contract/src/types.ts @@ -5,7 +5,7 @@ import type { ApiBase } from '@polkadot/api/base'; import type { ApiTypes } from '@polkadot/api/types'; import type { Text, u64 } from '@polkadot/types'; import type { ContractExecResultResult, ContractSelector, StorageDeposit } from '@polkadot/types/interfaces'; -import type { Codec, TypeDef } from '@polkadot/types/types'; +import type { Codec, ICompact, INumber, TypeDef } from '@polkadot/types/types'; import type { BN } from '@polkadot/util'; import type { Abi } from '.'; @@ -73,7 +73,7 @@ export interface DecodedMessage { } export interface ContractOptions { - gasLimit?: bigint | string | number | BN; + gasLimit?: bigint | string | number | BN | WeightV2; storageDepositLimit?: bigint | string | number | BN | null; value?: bigint | BN | string | number; } @@ -81,3 +81,15 @@ export interface ContractOptions { export interface BlueprintOptions extends ContractOptions { salt?: Uint8Array | string | null; } + +export interface WeightV2 { + refTime: ICompact; + proofSize: ICompact; +} + +export interface WeightAll { + v1Weight: BN; + v2Weight: { + refTime: BN; + }; +}