From cb97fd1a48c280a939208378a22d479981061744 Mon Sep 17 00:00:00 2001 From: Jaco Date: Thu, 4 Nov 2021 10:20:03 +0100 Subject: [PATCH] Use new object utilities (as applicable) (#4161) * Use new object utilities (as applicable) * Extract utility functions * Renames * Comment cleanup * Fix types --- packages/api/src/base/Decorate.ts | 8 +- packages/api/src/base/Init.ts | 8 +- packages/api/src/base/index.ts | 10 +- packages/api/src/promise/index.ts | 9 +- packages/api/src/rx/index.ts | 9 +- packages/api/src/submittable/createClass.ts | 138 ++++++++++---------- packages/api/src/util/augmentObject.ts | 12 +- 7 files changed, 87 insertions(+), 107 deletions(-) diff --git a/packages/api/src/base/Decorate.ts b/packages/api/src/base/Decorate.ts index fa81f43aed..4ed1cdf649 100644 --- a/packages/api/src/base/Decorate.ts +++ b/packages/api/src/base/Decorate.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { Observable } from 'rxjs'; +import type { DeriveCustom } from '@polkadot/api-derive'; import type { RpcInterface } from '@polkadot/rpc-core/types'; import type { Option, Raw, StorageKey, Text, u64 } from '@polkadot/types'; import type { Call, Hash, RuntimeVersion } from '@polkadot/types/interfaces'; @@ -18,7 +19,7 @@ import { ExactDerive, getAvailableDerives } from '@polkadot/api-derive'; import { memo, RpcCore } from '@polkadot/rpc-core'; import { WsProvider } from '@polkadot/rpc-provider'; import { expandMetadata, lazyMethod, lazyMethods, Metadata, TypeRegistry, unwrapStorageType } from '@polkadot/types'; -import { arrayChunk, arrayFlatten, assert, BN, BN_ZERO, compactStripLength, logger, u8aToHex } from '@polkadot/util'; +import { arrayChunk, arrayFlatten, assert, BN, BN_ZERO, compactStripLength, logger, objectSpread, u8aToHex } from '@polkadot/util'; import { createSubmittable } from '../submittable'; import { augmentObject } from '../util/augmentObject'; @@ -721,10 +722,7 @@ export abstract class Decorate extends Events { const specName = this._runtimeVersion?.specName.toString(); // Pull in derive from api-derive - const available = getAvailableDerives(this.#instanceId, this._rx, { - ...this._options.derives, - ...(this._options.typesBundle?.spec?.[specName || '']?.derives || {}) - }); + const available = getAvailableDerives(this.#instanceId, this._rx, objectSpread({}, this._options.derives, this._options.typesBundle?.spec?.[specName || '']?.derives)); return decorateDeriveSections<'rxjs', ExactDerive>(decorateMethod, available); } diff --git a/packages/api/src/base/Init.ts b/packages/api/src/base/Init.ts index d1731e57ad..021a3a48e9 100644 --- a/packages/api/src/base/Init.ts +++ b/packages/api/src/base/Init.ts @@ -3,6 +3,7 @@ import type { Observable, Subscription } from 'rxjs'; import type { Text } from '@polkadot/types'; +import type { ExtDef } from '@polkadot/types/extrinsic/signedExtensions/types'; import type { ChainProperties, Hash, RuntimeVersion, RuntimeVersionPartial } from '@polkadot/types/interfaces'; import type { Registry } from '@polkadot/types/types'; import type { BN } from '@polkadot/util'; @@ -13,7 +14,7 @@ import { firstValueFrom, map, of, switchMap } from 'rxjs'; import { Metadata, TypeRegistry } from '@polkadot/types'; import { getSpecAlias, getSpecExtensions, getSpecHasher, getSpecRpc, getSpecTypes, getUpgradeVersion } from '@polkadot/types-known'; -import { assert, BN_ZERO, isUndefined, logger, stringify, u8aEq, u8aToHex, u8aToU8a } from '@polkadot/util'; +import { assert, BN_ZERO, isUndefined, logger, objectSpread, stringify, u8aEq, u8aToHex, u8aToU8a } from '@polkadot/util'; import { cryptoWaitReady } from '@polkadot/util-crypto'; import { detectedCapabilities } from './capabilities'; @@ -94,10 +95,7 @@ export abstract class Init extends Decorate { registry.knownTypes.typesAlias = getSpecAlias(registry, chain, version.specName); } - registry.setMetadata(metadata, undefined, { - ...getSpecExtensions(registry, chain, version.specName), - ...(this._options.signedExtensions || {}) - }); + registry.setMetadata(metadata, undefined, objectSpread({}, getSpecExtensions(registry, chain, version.specName), this._options.signedExtensions)); } /** diff --git a/packages/api/src/base/index.ts b/packages/api/src/base/index.ts index 6a43fa841b..7f1a9bfb83 100644 --- a/packages/api/src/base/index.ts +++ b/packages/api/src/base/index.ts @@ -4,7 +4,7 @@ import type { SignerPayloadRawBase } from '@polkadot/types/types'; import type { ApiOptions, ApiTypes, DecorateMethod, Signer } from '../types'; -import { assert, isString, u8aToHex, u8aToU8a } from '@polkadot/util'; +import { assert, isString, objectSpread, u8aToHex, u8aToU8a } from '@polkadot/util'; import { Getters } from './Getters'; @@ -72,11 +72,9 @@ export abstract class ApiBase extends Getters assert(_signer?.signRaw, 'No signer exists with a signRaw interface. You possibly need to pass through an explicit keypair for the origin so it can be used for signing.'); return ( - await _signer.signRaw({ - type: 'bytes', - ...data, - address - }) + await _signer.signRaw( + objectSpread({ type: 'bytes' }, data, { address }) + ) ).signature; } diff --git a/packages/api/src/promise/index.ts b/packages/api/src/promise/index.ts index 2d2ff72171..9aeea2a821 100644 --- a/packages/api/src/promise/index.ts +++ b/packages/api/src/promise/index.ts @@ -7,7 +7,7 @@ import type { ApiOptions, DecorateFn, DecorateMethodOptions, ObsInnerType, Stora import { catchError, EMPTY, Subscription, tap } from 'rxjs'; -import { assert, isFunction } from '@polkadot/util'; +import { assert, isFunction, objectSpread } from '@polkadot/util'; import { ApiBase } from '../base'; import { Combinator, CombinatorCallback, CombinatorFunction } from './Combinator'; @@ -277,10 +277,9 @@ export class ApiPromise extends ApiBase<'promise'> { * @description Returns a clone of this ApiPromise instance (new underlying provider connection) */ public clone (): ApiPromise { - return new ApiPromise({ - ...this._options, - source: this - }); + return new ApiPromise( + objectSpread({}, this._options, { source: this }) + ); } /** diff --git a/packages/api/src/rx/index.ts b/packages/api/src/rx/index.ts index 91e6268fb4..349f6c6243 100644 --- a/packages/api/src/rx/index.ts +++ b/packages/api/src/rx/index.ts @@ -6,6 +6,8 @@ import type { ApiOptions, DecorateFn } from '../types'; import { from, Observable } from 'rxjs'; +import { objectSpread } from '@polkadot/util'; + import { ApiBase } from '../base'; export function decorateMethod > (method: Method): Method { @@ -180,9 +182,8 @@ export class ApiRx extends ApiBase<'rxjs'> { * @description Returns a clone of this ApiRx instance (new underlying provider connection) */ public clone (): ApiRx { - return new ApiRx({ - ...this._options, - source: this - }); + return new ApiRx( + objectSpread({}, this._options, { source: this }) + ); } } diff --git a/packages/api/src/submittable/createClass.ts b/packages/api/src/submittable/createClass.ts index 73d386640e..6740734c32 100644 --- a/packages/api/src/submittable/createClass.ts +++ b/packages/api/src/submittable/createClass.ts @@ -11,7 +11,7 @@ import type { AddressOrPair, SignerOptions, SubmittableDryRunResult, Submittable import { catchError, first, map, mapTo, mergeMap, of, switchMap, tap } from 'rxjs'; -import { assert, isBn, isFunction, isNumber, isString, isU8a } from '@polkadot/util'; +import { assert, isBn, isFunction, isNumber, isString, isU8a, objectSpread } from '@polkadot/util'; import { ApiBase } from '../base'; import { filterEvents, isKeyringPair } from '../util'; @@ -25,6 +25,55 @@ interface SubmittableOptions { const identity = (input: T): T => input; +function makeEraOptions (api: ApiInterfaceRx, registry: Registry, partialOptions: Partial, { header, mortalLength, nonce }: { header: Header | null; mortalLength: number; nonce: Index }): SignatureOptions { + if (!header) { + if (isNumber(partialOptions.era)) { + // since we have no header, it is immortal, remove any option overrides + // so we only supply the genesisHash and no era to the construction + delete partialOptions.era; + delete partialOptions.blockHash; + } + + return makeSignOptions(api, partialOptions, { nonce }); + } + + return makeSignOptions(api, partialOptions, { + blockHash: header.hash, + era: registry.createType('ExtrinsicEra', { + current: header.number, + period: partialOptions.era || mortalLength + }), + nonce + }); +} + +function makeSignAndSendOptions (partialOptions?: Partial | Callback, statusCb?: Callback): [Partial, Callback?] { + let options: Partial = {}; + + if (isFunction(partialOptions)) { + statusCb = partialOptions; + } else { + options = objectSpread({}, partialOptions); + } + + return [options, statusCb]; +} + +function makeSignOptions (api: ApiInterfaceRx, partialOptions: Partial, extras: { blockHash?: Hash; era?: ExtrinsicEra; nonce?: Index }): SignatureOptions { + return objectSpread( + { blockHash: api.genesisHash, genesisHash: api.genesisHash }, + partialOptions, + extras, + { runtimeVersion: api.runtimeVersion, signedExtensions: api.registry.signedExtensions, version: api.extrinsicType } + ); +} + +function optionsOrNonce (partialOptions: Partial = {}): Partial { + return isBn(partialOptions) || isNumber(partialOptions) + ? { nonce: partialOptions } + : partialOptions; +} + export function createClass ({ api, apiType, decorateMethod }: SubmittableOptions): Constructor> { // an instance of the base extrinsic for us to extend const ExtrinsicBase = api.registry.createClass('Extrinsic'); @@ -67,7 +116,7 @@ export function createClass ({ api, apiType, decorate ); } - const [allOptions] = this.#makeSignAndSendOptions(optionsOrHash); + const [allOptions] = makeSignAndSendOptions(optionsOrHash); const address = isKeyringPair(account) ? account.address : account.toString(); // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call @@ -77,8 +126,8 @@ export function createClass ({ api, apiType, decorate first(), switchMap((signingInfo): Observable => { // setup our options (same way as in signAndSend) - const eraOptions = this.#makeEraOptions(allOptions, signingInfo); - const signOptions = this.#makeSignOptions(eraOptions, {}); + const eraOptions = makeEraOptions(api, this.registry, allOptions, signingInfo); + const signOptions = makeSignOptions(api, eraOptions, {}); this.signFake(address, signOptions); @@ -110,8 +159,8 @@ export function createClass ({ api, apiType, decorate * @description Sign a transaction, returning the this to allow chaining, i.e. .sign(...).send(). When options, e.g. nonce/blockHash are not specified, it will be inferred. To retrieve eg. nonce use `signAsync` (the preferred interface, this is provided for backwards compatibility) * @deprecated */ - public override sign (account: IKeyringPair, optionsOrNonce?: Partial): this { - super.sign(account, this.#makeSignOptions(this.#optionsOrNonce(optionsOrNonce), {})); + public override sign (account: IKeyringPair, partialOptions?: Partial): this { + super.sign(account, makeSignOptions(api, optionsOrNonce(partialOptions), {})); return this; } @@ -119,26 +168,26 @@ export function createClass ({ api, apiType, decorate /** * @description Signs a transaction, returning `this` to allow chaining. E.g.: `sign(...).send()`. Like `.signAndSend` this will retrieve the nonce and blockHash to send the tx with. */ - public signAsync (account: AddressOrPair, optionsOrNonce?: Partial): SubmittableThis { + public signAsync (account: AddressOrPair, partialOptions?: Partial): SubmittableThis { // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call return decorateMethod( (): Observable => - this.#observeSign(account, optionsOrNonce).pipe(mapTo(this) as OperatorFunction) + this.#observeSign(account, partialOptions).pipe(mapTo(this)) )(); } // signAndSend with an immediate Hash result - public signAndSend (account: AddressOrPair, options?: Partial): SubmittableResultResult; + public signAndSend (account: AddressOrPair, partialOptions?: Partial): SubmittableResultResult; // signAndSend with a subscription, i.e. callback provided public signAndSend (account: AddressOrPair, statusCb: Callback): SubmittableResultSubscription; // signAndSend with options and a callback - public signAndSend (account: AddressOrPair, options: Partial, statusCb?: Callback): SubmittableResultSubscription; + public signAndSend (account: AddressOrPair, partialOptions: Partial, statusCb?: Callback): SubmittableResultSubscription; // signAndSend implementation for all 3 cases above - public signAndSend (account: AddressOrPair, optionsOrStatus?: Partial | Callback, optionalStatusCb?: Callback): SubmittableResultResult | SubmittableResultSubscription { - const [options, statusCb] = this.#makeSignAndSendOptions(optionsOrStatus, optionalStatusCb); + public signAndSend (account: AddressOrPair, partialOptions?: Partial | Callback, optionalStatusCb?: Callback): SubmittableResultResult | SubmittableResultSubscription { + const [options, statusCb] = makeSignAndSendOptions(partialOptions, optionalStatusCb); const isSubscription = api.hasSubscriptions && (this.#ignoreStatusCb || !!statusCb); // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call @@ -161,61 +210,15 @@ export function createClass ({ api, apiType, decorate return this; } - #makeEraOptions = (options: Partial, { header, mortalLength, nonce }: { header: Header | null; mortalLength: number; nonce: Index }): SignatureOptions => { - if (!header) { - if (isNumber(options.era)) { - // since we have no header, it is immortal, remove any option overrides - // so we only supply the genesisHash and no era to the construction - delete options.era; - delete options.blockHash; - } - - return this.#makeSignOptions(options, { nonce }); - } - - return this.#makeSignOptions(options, { - blockHash: header.hash, - era: this.registry.createType('ExtrinsicEra', { - current: header.number, - period: options.era || mortalLength - }), - nonce - }); - }; - - #makeSignOptions = (options: Partial, extras: { blockHash?: Hash; era?: ExtrinsicEra; nonce?: Index }): SignatureOptions => { - return { - blockHash: api.genesisHash, - genesisHash: api.genesisHash, - ...options, - ...extras, - runtimeVersion: api.runtimeVersion, - signedExtensions: api.registry.signedExtensions, - version: api.extrinsicType - } as SignatureOptions; - }; - - #makeSignAndSendOptions = (optionsOrStatus?: Partial | Callback, statusCb?: Callback): [Partial, Callback?] => { - let options: Partial = {}; - - if (isFunction(optionsOrStatus)) { - statusCb = optionsOrStatus; - } else { - options = { ...optionsOrStatus }; - } - - return [options, statusCb]; - }; - - #observeSign = (account: AddressOrPair, optionsOrNonce?: Partial): Observable => { + #observeSign = (account: AddressOrPair, partialOptions?: Partial): Observable => { const address = isKeyringPair(account) ? account.address : account.toString(); - const options = this.#optionsOrNonce(optionsOrNonce); + const options = optionsOrNonce(partialOptions); let updateId: number | undefined; return api.derive.tx.signingInfo(address, options.nonce, options.era).pipe( first(), mergeMap(async (signingInfo): Promise => { - const eraOptions = this.#makeEraOptions(options, signingInfo); + const eraOptions = makeEraOptions(api, this.registry, options, signingInfo); if (isKeyringPair(account)) { this.sign(account, eraOptions); @@ -270,25 +273,16 @@ export function createClass ({ api, apiType, decorate ); }; - // NOTE here we actually override nonce if it was specified (backwards compat for - // the previous signature - don't let user space break, but allow then time to upgrade) - #optionsOrNonce = (optionsOrNonce: Partial = {}): Partial => { - return isBn(optionsOrNonce) || isNumber(optionsOrNonce) - ? { nonce: optionsOrNonce } - : optionsOrNonce; - }; - #signViaSigner = async (address: Address | string | Uint8Array, options: SignatureOptions, header: Header | null): Promise => { const signer = options.signer || api.signer; assert(signer, 'No signer specified, either via api.setSigner or via sign options. You possibly need to pass through an explicit keypair for the origin so it can be used for signing.'); - const payload = this.registry.createType('SignerPayload', { - ...options, + const payload = this.registry.createType('SignerPayload', objectSpread({}, options, { address, blockNumber: header ? header.number : 0, method: this.method - }); + })); let result: SignerResult; if (signer.signPayload) { diff --git a/packages/api/src/util/augmentObject.ts b/packages/api/src/util/augmentObject.ts index 173c2190cf..a2b413e9c9 100644 --- a/packages/api/src/util/augmentObject.ts +++ b/packages/api/src/util/augmentObject.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { lazyMethod } from '@polkadot/types'; -import { logger } from '@polkadot/util'; +import { logger, objectClear } from '@polkadot/util'; type Sections = Record>; @@ -12,14 +12,6 @@ type StringsStrings = [string[], string[]]; const l = logger('api/augment'); -function clearObject (obj: Record): void { - const keys = Object.keys(obj); - - for (let i = 0; i < keys.length; i++) { - delete obj[keys[i]]; - } -} - function logLength (type: 'added' | 'removed', values: string[], and: string[] = []): string { return values.length ? ` ${values.length} ${type}${and.length ? ' and' : ''}` @@ -106,7 +98,7 @@ function lazySection (src: Methods, dst: Methods): void { * @internal */ export function augmentObject (prefix: string | null, src: Sections, dst: Sections, fromEmpty = false): Sections { - fromEmpty && clearObject(dst); + fromEmpty && objectClear(dst); // NOTE: This part is slightly problematic since it will get the // values for at least all the sections and the names of the methods