diff --git a/packages/page-referenda/src/Referenda/RefOngoing.tsx b/packages/page-referenda/src/Referenda/RefOngoing.tsx index 7c2010c415..13deba6a3b 100644 --- a/packages/page-referenda/src/Referenda/RefOngoing.tsx +++ b/packages/page-referenda/src/Referenda/RefOngoing.tsx @@ -1,6 +1,7 @@ // Copyright 2017-2023 @polkadot/app-referenda authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { ApiPromise } from '@polkadot/api'; import type { Hash } from '@polkadot/types/interfaces'; import type { PalletConvictionVotingTally, PalletRankedCollectiveTally, PalletReferendaDeposit, PalletReferendaTrackInfo } from '@polkadot/types/lookup'; import type { BN } from '@polkadot/util'; @@ -10,7 +11,8 @@ import type { Referendum, ReferendumProps as Props } from '../types'; import React, { useMemo } from 'react'; import { CallExpander, Progress } from '@polkadot/react-components'; -import { getPreimageHash, usePreimage } from '@polkadot/react-hooks/usePreimage'; +import { useApi, usePreimage } from '@polkadot/react-hooks'; +import { getPreimageHash } from '@polkadot/react-hooks/usePreimage'; import { useTranslation } from '../translate'; import Deposits from './Deposits'; @@ -27,15 +29,15 @@ interface Expanded { decideEnd: BN | null; confirmEnd: BN | null; }; - proposalHash: HexString; + proposalHash?: HexString; submissionDeposit: PalletReferendaDeposit | null; tally: PalletConvictionVotingTally | PalletRankedCollectiveTally; tallyTotal: BN; } -function expandOngoing (info: Referendum['info'], track?: PalletReferendaTrackInfo): Expanded { +function expandOngoing (api: ApiPromise, info: Referendum['info'], track?: PalletReferendaTrackInfo): Expanded { const ongoing = info.asOngoing; - const proposalHash = getPreimageHash(ongoing.proposal || (ongoing as unknown as { proposalHash: Hash }).proposalHash); + const proposalHash = getPreimageHash(api, ongoing.proposal || (ongoing as unknown as { proposalHash: Hash }).proposalHash).proposalHash; let prepareEnd: BN | null = null; let decideEnd: BN | null = null; let confirmEnd: BN | null = null; @@ -76,10 +78,11 @@ function expandOngoing (info: Referendum['info'], track?: PalletReferendaTrackIn function Ongoing ({ isMember, members, palletReferenda, palletVote, ranks, trackInfo, value: { id, info, isConvictionVote, track } }: Props): React.ReactElement { const { t } = useTranslation(); + const { api } = useApi(); const { decisionDeposit, periods: { confirmEnd, decideEnd, periodEnd }, proposalHash, submissionDeposit, tally, tallyTotal } = useMemo( - () => expandOngoing(info, track), - [info, track] + () => expandOngoing(api, info, track), + [api, info, track] ); const preimage = usePreimage(proposalHash); diff --git a/packages/react-hooks/src/types.ts b/packages/react-hooks/src/types.ts index 051e596f54..de5dbdd6af 100644 --- a/packages/react-hooks/src/types.ts +++ b/packages/react-hooks/src/types.ts @@ -5,7 +5,6 @@ import type { ApiPromise } from '@polkadot/api'; import type { SubmittableExtrinsic } from '@polkadot/api/types'; import type { DeriveAccountFlags, DeriveAccountRegistration } from '@polkadot/api-derive/types'; import type { DisplayedJudgement } from '@polkadot/react-components/types'; -import type { Bytes } from '@polkadot/types'; import type { AccountId, Balance, BlockNumber, Call, Exposure, Hash, RewardDestination, SessionIndex, StakingLedger, ValidatorPrefs } from '@polkadot/types/interfaces'; import type { PalletPreimageRequestStatus } from '@polkadot/types/lookup'; import type { IExtrinsic, Registry } from '@polkadot/types/types'; @@ -181,6 +180,7 @@ export interface PreimageStatus { count: number; deposit?: PreimageDeposit; isCompleted: boolean; + isHashParam: boolean; proposalHash: HexString; proposalLength?: BN; registry: Registry; @@ -188,7 +188,6 @@ export interface PreimageStatus { } export interface PreimageBytes { - bytes?: Bytes | null; proposal?: Call | null; proposalError?: string | null; proposalWarning?: string | null; diff --git a/packages/react-hooks/src/usePreimage.ts b/packages/react-hooks/src/usePreimage.ts index 118b260359..eee0c6055d 100644 --- a/packages/react-hooks/src/usePreimage.ts +++ b/packages/react-hooks/src/usePreimage.ts @@ -13,17 +13,28 @@ import { useMemo } from 'react'; import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks'; import { Option } from '@polkadot/types'; -import { BN, BN_ZERO, formatNumber, isString, objectSpread } from '@polkadot/util'; +import { BN, BN_ZERO, formatNumber, isString, isU8a, objectSpread, u8aToHex } from '@polkadot/util'; -type BytesParams = [[proposalHash: HexString, proposalLength: BN]] | [proposalHash: HexString]; +type BytesParamsType = [[proposalHash: HexString, proposalLength: BN]] | [proposalHash: HexString]; -interface InterimResult { - paramsBytes?: BytesParams; - preimageStatus?: PreimageStatus; +interface BytesParams { + paramsBytes?: BytesParamsType; + resultPreimageFor?: PreimageStatus; +} + +interface StatusParams { + inlineData?: Uint8Array; + paramsStatus?: [HexString]; + proposalHash?: HexString; + resultPreimageHash?: PreimageStatus; } type Result = 'unknown' | 'hash' | 'hashAndLen'; +/** + * @internal Determine if we are working with current generation (H256,u32) + * or previous generation H256 params to the preimageFor storage entry + */ export function getParamType (api: ApiPromise): Result { if (( api.query.preimage && @@ -44,21 +55,64 @@ export function getParamType (api: ApiPromise): Result { return 'unknown'; } -function createResult (api: ApiPromise, preimageStatus: PreimageStatus, optBytes: Option): Preimage { - const bytes = optBytes.unwrapOr(null); +/** @internal Unwraps a passed preimage hash into components */ +export function getPreimageHash (api: ApiPromise, hashOrBounded: Hash | HexString | FrameSupportPreimagesBounded): StatusParams { + let proposalHash: HexString | undefined; + let inlineData: Uint8Array | undefined; + + if (isString(hashOrBounded)) { + proposalHash = hashOrBounded; + } else if (isU8a(hashOrBounded)) { + proposalHash = hashOrBounded.toHex(); + } else { + const bounded = hashOrBounded; + + if (bounded.isInline) { + inlineData = bounded.asInline.toU8a(true); + proposalHash = u8aToHex(api.registry.hash(inlineData)); + } else if (hashOrBounded.isLegacy) { + proposalHash = hashOrBounded.asLegacy.hash_.toHex(); + } else if (hashOrBounded.isLookup) { + proposalHash = hashOrBounded.asLookup.hash_.toHex(); + } else { + console.error(`Unhandled FrameSupportPreimagesBounded type ${hashOrBounded.type}`); + } + } + + return { + inlineData, + paramsStatus: proposalHash && [proposalHash], + proposalHash, + resultPreimageHash: proposalHash && { + count: 0, + isCompleted: false, + isHashParam: getParamType(api) === 'hash', + proposalHash, + proposalLength: inlineData && new BN(inlineData.length), + registry: api.registry, + status: null + } + }; +} + +/** @internal Creates a final result */ +function createResult (interimResult: PreimageStatus, optBytes: Option | Uint8Array): Preimage { + const callData = isU8a(optBytes) + ? optBytes + : optBytes.unwrapOr(null); let proposal: Call | null = null; let proposalError: string | null = null; let proposalWarning: string | null = null; let proposalLength: BN | undefined; - if (bytes) { + if (callData) { try { - proposal = api.registry.createType('Call', bytes.toU8a(true)); + proposal = interimResult.registry.createType('Call', callData); const callLength = proposal.encodedLength; - if (preimageStatus.proposalLength) { - const storeLength = preimageStatus.proposalLength.toNumber(); + if (interimResult.proposalLength) { + const storeLength = interimResult.proposalLength.toNumber(); if (callLength !== storeLength) { proposalWarning = `Decoded call length does not match on-chain stored preimage length (${formatNumber(callLength)} bytes vs ${formatNumber(storeLength)} bytes)`; @@ -76,17 +130,16 @@ function createResult (api: ApiPromise, preimageStatus: PreimageStatus, optBytes proposalWarning = 'No preimage bytes found'; } - return objectSpread({}, preimageStatus, { - bytes, + return objectSpread({}, interimResult, { isCompleted: true, proposal, proposalError, - proposalLength: proposalLength || preimageStatus.proposalLength, - proposalWarning, - registry: api.registry + proposalLength: proposalLength || interimResult.proposalLength, + proposalWarning }); } +/** @internal Helper to unwrap a deposit tuple into a structure */ function convertDeposit (deposit?: [AccountId, Balance] | null): PreimageDeposit | undefined { return deposit ? { @@ -96,22 +149,15 @@ function convertDeposit (deposit?: [AccountId, Balance] | null): PreimageDeposit : undefined; } -function getBytesParams (api: ApiPromise, hash: Hash | HexString, optStatus: Option): InterimResult { - const isHashParam = getParamType(api) === 'hash'; - const status = optStatus.unwrapOr(null); - const preimageStatus: PreimageStatus = { - count: 0, - isCompleted: false, - proposalHash: isString(hash) - ? hash - : hash.toHex(), - registry: api.registry, - status - }; +/** @internal Returns the parameters required for a call to bytes */ +function getBytesParams (interimResult: PreimageStatus, optStatus: Option): BytesParams { + const result = objectSpread({}, interimResult, { + status: optStatus.unwrapOr(null) + }); - if (status) { - if (status.isRequested) { - const asRequested = status.asRequested; + if (result.status) { + if (result.status.isRequested) { + const asRequested = result.status.asRequested; if (asRequested instanceof Option) { // FIXME Cannot recall how to deal with these @@ -119,87 +165,72 @@ function getBytesParams (api: ApiPromise, hash: Hash | HexString, optStatus: Opt } else { const { count, deposit, len } = asRequested; - preimageStatus.count = count.toNumber(); - preimageStatus.deposit = convertDeposit(deposit.unwrapOr(null)); - preimageStatus.proposalLength = len.unwrapOr(BN_ZERO); + result.count = count.toNumber(); + result.deposit = convertDeposit(deposit.unwrapOr(null)); + result.proposalLength = len.unwrapOr(BN_ZERO); } - } else if (status.isUnrequested) { - const asUnrequested = status.asUnrequested; + } else if (result.status.isUnrequested) { + const asUnrequested = result.status.asUnrequested; if (asUnrequested instanceof Option) { - preimageStatus.deposit = convertDeposit( + result.deposit = convertDeposit( // old-style conversion (asUnrequested as Option>).unwrapOr(null) ); } else { - const { deposit, len } = status.asUnrequested; + const { deposit, len } = result.status.asUnrequested; - preimageStatus.deposit = convertDeposit(deposit); - preimageStatus.proposalLength = len; + result.deposit = convertDeposit(deposit); + result.proposalLength = len; } } else { - // for future reference... - console.error('Unhandled preimage status type: ', status.type); + console.error(`Unhandled PalletPreimageRequestStatus type: ${result.status.type}`); } } return { - paramsBytes: isHashParam - ? [preimageStatus.proposalHash] - : [[preimageStatus.proposalHash, preimageStatus.proposalLength || BN_ZERO]], - preimageStatus + paramsBytes: result.isHashParam + ? [result.proposalHash] + : [[result.proposalHash, result.proposalLength || BN_ZERO]], + resultPreimageFor: result }; } -export function getPreimageHash (hashOrBounded: Hash | HexString | FrameSupportPreimagesBounded): HexString { - if (isString(hashOrBounded)) { - return hashOrBounded; - } - - const bounded = hashOrBounded as FrameSupportPreimagesBounded; - - return bounded.isInline - ? bounded.asInline.hash.toHex() - : bounded.isLegacy - ? bounded.asLegacy.hash_.toHex() - : bounded.isLookup - ? bounded.asLookup.hash_.toHex() - : hashOrBounded.toHex(); -} - function usePreimageImpl (hashOrBounded?: Hash | HexString | FrameSupportPreimagesBounded | null): Preimage | undefined { const { api } = useApi(); // retrieve the status using only the hash of the image - const paramsStatus = useMemo( - // we need a hash _and_ be on the newest supported version of the pallet - // (after the application of bounded calls) + const { inlineData, paramsStatus, resultPreimageHash } = useMemo( () => hashOrBounded - ? [getPreimageHash(hashOrBounded)] - : undefined, - [hashOrBounded] + ? getPreimageHash(api, hashOrBounded) + : {}, + [api, hashOrBounded] ); const optStatus = useCall>(paramsStatus && api.query.preimage?.statusFor, paramsStatus); // from the retrieved status (if any), get the on-chain stored bytes - const { paramsBytes, preimageStatus } = useMemo( - () => paramsStatus && optStatus - ? getBytesParams(api, paramsStatus[0], optStatus) + const { paramsBytes, resultPreimageFor } = useMemo( + () => resultPreimageHash && optStatus + ? getBytesParams(resultPreimageHash, optStatus) : {}, - [api, optStatus, paramsStatus] + [optStatus, resultPreimageHash] ); const optBytes = useCall>(paramsBytes && api.query.preimage?.preimageFor, paramsBytes); // extract all the preimage info we have retrieved return useMemo( - () => preimageStatus + () => resultPreimageFor ? optBytes - ? createResult(api, preimageStatus, optBytes) - : preimageStatus - : undefined, - [api, optBytes, preimageStatus] + ? createResult(resultPreimageFor, optBytes) + : resultPreimageFor + : resultPreimageHash + ? inlineData + ? createResult(resultPreimageHash, inlineData) + : resultPreimageHash + : undefined, + [inlineData, optBytes, resultPreimageHash, resultPreimageFor] ); }