diff --git a/packages/page-contracts/src/Codes/Upload.tsx b/packages/page-contracts/src/Codes/Upload.tsx index 3671866c3b46..d0bb3fc5d9f5 100644 --- a/packages/page-contracts/src/Codes/Upload.tsx +++ b/packages/page-contracts/src/Codes/Upload.tsx @@ -88,7 +88,7 @@ function Upload ({ onClose }: Props): React.ReactElement { try { contract = code && contractAbi?.constructors[constructorIndex]?.method && value ? code.tx[contractAbi.constructors[constructorIndex].method]({ - gasLimit: weight.weight, + gasLimit: weight.isWeightV2 ? weight.weightV2 : weight.weight, storageDepositLimit: null, value: contractAbi?.constructors[constructorIndex].isPayable ? value : undefined }, ...params) diff --git a/packages/page-contracts/src/Contracts/Call.tsx b/packages/page-contracts/src/Contracts/Call.tsx index 4a59607f8ca6..be5dcd7d9895 100644 --- a/packages/page-contracts/src/Contracts/Call.tsx +++ b/packages/page-contracts/src/Contracts/Call.tsx @@ -4,13 +4,13 @@ import type { SubmittableExtrinsic } from '@polkadot/api/types'; import type { ContractPromise } from '@polkadot/api-contract'; import type { ContractCallOutcome } from '@polkadot/api-contract/types'; -import type { CallResult } from './types.js'; +import type { WeightV2 } from '@polkadot/types/interfaces'; +import type { CallResult } from './types'; import React, { useCallback, useEffect, useState } from 'react'; import { Button, Dropdown, Expander, InputAddress, InputBalance, Modal, styled, Toggle, TxButton } from '@polkadot/react-components'; -import { useAccountId, useDebounce, useFormField, useToggle } from '@polkadot/react-hooks'; -import { convertWeight } from '@polkadot/react-hooks/useWeight'; +import { useAccountId, useApi, useDebounce, useFormField, useToggle } from '@polkadot/react-hooks'; import { Available } from '@polkadot/react-query'; import { BN, BN_ONE, BN_ZERO } from '@polkadot/util'; @@ -33,9 +33,11 @@ const MAX_CALL_WEIGHT = new BN(5_000_000_000_000).isub(BN_ONE); function Call ({ className = '', contract, messageIndex, onCallResult, onChangeMessage, onClose }: Props): React.ReactElement | null { const { t } = useTranslation(); + const { api } = useApi(); const message = contract.abi.messages[messageIndex]; const [accountId, setAccountId] = useAccountId(); const [estimatedWeight, setEstimatedWeight] = useState(null); + const [estimatedWeightV2, setEstimatedWeightV2] = useState(null); const [value, isValueValid, setValue] = useFormField(BN_ZERO); const [outcomes, setOutcomes] = useState([]); const [execTx, setExecTx] = useState | null>(null); @@ -47,14 +49,18 @@ function Call ({ className = '', contract, messageIndex, onCallResult, onChangeM useEffect((): void => { setEstimatedWeight(null); + setEstimatedWeightV2(null); setParams([]); }, [contract, messageIndex]); useEffect((): void => { value && message.isMutating && setExecTx((): SubmittableExtrinsic<'promise'> | null => { try { - return contract.tx[message.method]({ gasLimit: weight.weight, storageDepositLimit: null, value: message.isPayable ? value : 0 }, ...params); - } catch { + return contract.tx[message.method]( + { gasLimit: weight.isWeightV2 ? weight.weightV2 : weight.weight, storageDepositLimit: null, value: message.isPayable ? value : 0 }, + ...params + ); + } catch (error) { return null; } }); @@ -67,13 +73,26 @@ function Call ({ className = '', contract, messageIndex, onCallResult, onChangeM contract .query[message.method](accountId, { gasLimit: -1, storageDepositLimit: null, value: message.isPayable ? dbValue : 0 }, ...dbParams) - .then(({ gasRequired, result }) => setEstimatedWeight( - result.isOk - ? convertWeight(gasRequired).v1Weight - : null - )) - .catch(() => setEstimatedWeight(null)); - }, [accountId, contract, message, dbParams, dbValue]); + .then(({ gasRequired, result }) => { + if (weight.isWeightV2) { + setEstimatedWeightV2( + result.isOk + ? api.registry.createType('WeightV2', gasRequired) + : null + ); + } else { + setEstimatedWeight( + result.isOk + ? gasRequired + : null + ); + } + }) + .catch(() => { + setEstimatedWeight(null); + setEstimatedWeightV2(null); + }); + }, [api, accountId, contract, message, dbParams, dbValue, weight.isWeightV2]); const _onSubmitRpc = useCallback( (): void => { @@ -82,7 +101,11 @@ function Call ({ className = '', contract, messageIndex, onCallResult, onChangeM } contract - .query[message.method](accountId, { gasLimit: weight.isEmpty ? -1 : weight.weight, storageDepositLimit: null, value: message.isPayable ? value : 0 }, ...params) + .query[message.method]( + accountId, + { gasLimit: weight.isWeightV2 ? weight.weightV2 : weight.isEmpty ? -1 : weight.weight, storageDepositLimit: null, value: message.isPayable ? value : 0 }, + ...params + ) .then((result): void => { setOutcomes([{ ...result, diff --git a/packages/page-contracts/src/Contracts/Deploy.tsx b/packages/page-contracts/src/Contracts/Deploy.tsx index 8a36fd248c2f..9399a9a1ddef 100644 --- a/packages/page-contracts/src/Contracts/Deploy.tsx +++ b/packages/page-contracts/src/Contracts/Deploy.tsx @@ -79,7 +79,7 @@ function Deploy ({ codeHash, constructorIndex = 0, onClose, setConstructorIndex if (blueprint && contractAbi?.constructors[constructorIndex]?.method) { try { return blueprint.tx[contractAbi.constructors[constructorIndex].method]({ - gasLimit: weight.weight, + gasLimit: weight.isWeightV2 ? weight.weightV2 : weight.weight, salt: withSalt ? salt : null, diff --git a/packages/page-contracts/src/shared/InputMegaGas.tsx b/packages/page-contracts/src/shared/InputMegaGas.tsx index c6700ba3b801..76f593851469 100644 --- a/packages/page-contracts/src/shared/InputMegaGas.tsx +++ b/packages/page-contracts/src/shared/InputMegaGas.tsx @@ -1,24 +1,42 @@ // Copyright 2017-2023 @polkadot/app-contracts authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { WeightV2 } from '@polkadot/types/interfaces'; import type { BN } from '@polkadot/util'; import type { UseWeight } from '../types.js'; import React, { useEffect, useMemo, useState } from 'react'; -import { InputNumber, styled, Toggle } from '@polkadot/react-components'; +import { InputNumber, Toggle } from '@polkadot/react-components'; import { BN_MILLION, BN_ONE, BN_ZERO } from '@polkadot/util'; import { useTranslation } from '../translate.js'; interface Props { className?: string; - estimatedWeight?: BN | null; + estimatedWeight?: BN + estimatedWeightV2?: WeightV2; + help: React.ReactNode; isCall?: boolean; weight: UseWeight; } -function InputMegaGas ({ className, estimatedWeight, isCall, weight: { executionTime, isValid, megaGas, percentage, setIsEmpty, setMegaGas } }: Props): React.ReactElement { +function InputMegaGas ({ className, + estimatedWeight, + estimatedWeightV2, + help, + isCall, + weight: { executionTime, + isValid, + isWeightV2, + megaGas, + megaRefTime, + percentage, + proofSize, + setIsEmpty, + setMegaGas, + setMegaRefTime, + setProofSize } }: Props): React.ReactElement { const { t } = useTranslation(); const [withEstimate, setWithEstimate] = useState(true); @@ -29,10 +47,32 @@ function InputMegaGas ({ className, estimatedWeight, isCall, weight: { execution [estimatedWeight] ); + const estimatedMgRefTime = useMemo( + () => estimatedWeightV2 + ? estimatedWeightV2.refTime.toBn().div(BN_MILLION).iadd(BN_ONE) + : null, + [estimatedWeightV2] + ); + + const estimatedProofSize = useMemo( + () => estimatedWeightV2 + ? estimatedWeightV2.proofSize.toBn() + : null, + [estimatedWeightV2] + ); + useEffect((): void => { withEstimate && estimatedMg && setMegaGas(estimatedMg); }, [estimatedMg, setMegaGas, withEstimate]); + useEffect((): void => { + withEstimate && estimatedMgRefTime && setMegaRefTime(estimatedMgRefTime); + }, [estimatedMgRefTime, setMegaRefTime, withEstimate]); + + useEffect((): void => { + withEstimate && estimatedProofSize && setProofSize(estimatedProofSize); + }, [estimatedProofSize, setProofSize, withEstimate]); + useEffect((): void => { setIsEmpty(withEstimate && !!isCall); }, [isCall, setIsEmpty, withEstimate]); @@ -40,43 +80,85 @@ function InputMegaGas ({ className, estimatedWeight, isCall, weight: { execution const isDisabled = !!estimatedMg && withEstimate; return ( - - ('max gas allowed (M, {{estimatedMg}} estimated)', { replace: { estimatedMg: estimatedMg.toString() } }) - : t('max gas allowed (M)') - } - labelExtra={(estimatedWeight || isCall) && ( - + {isWeightV2 + ? <> + ('max RefTime allowed (M, {{estimatedRefTime}} estimated)', { replace: { estimatedMgRefTime: estimatedMgRefTime.toString() } }) + : t('max RefTime allowed (M)') + } + onChange={isDisabled ? undefined : setMegaRefTime} + value={isDisabled ? undefined : ((isCall && withEstimate) ? BN_ZERO : megaRefTime)} + > + {(estimatedWeightV2 || isCall) && ( + ('max read gas') + : t('use estimated gas') + } + onChange={setWithEstimate} + value={withEstimate} + /> + )} + + ('max read gas') - : t('use estimated gas') + estimatedProofSize && (isCall ? !withEstimate : true) + ? t('max ProofSize allowed ({{estimatedProofSize}} estimated)', { replace: { estimatedProofSize: estimatedProofSize.toString() } }) + : t('max ProofSize allowed') } - onChange={setWithEstimate} - value={withEstimate} + onChange={isDisabled ? undefined : setProofSize} + value={isDisabled ? undefined : ((isCall && withEstimate) ? BN_ZERO : proofSize)} /> - )} - onChange={isDisabled ? undefined : setMegaGas} - value={isDisabled ? undefined : ((isCall && withEstimate) ? BN_ZERO : megaGas)} - /> -
- {t('{{executionTime}}s execution time', { replace: { executionTime: executionTime.toFixed(3) } })}{', '} - {t('{{percentage}}% of block weight', { replace: { percentage: percentage.toFixed(2) } })} -
-
+
+ {t('{{executionTime}}s execution time', { replace: { executionTime: executionTime.toFixed(3) } })}{', '} + {t('{{percentage}}% of block weight', { replace: { percentage: percentage.toFixed(2) } })} +
+ + : <> + ('max gas allowed (M, {{estimatedMg}} estimated)', { replace: { estimatedMg: estimatedMg.toString() } }) + : t('max gas allowed (M)') + } + onChange={isDisabled ? undefined : setMegaGas} + value={isDisabled ? undefined : ((isCall && withEstimate) ? BN_ZERO : megaGas)} + > + {(estimatedWeight || isCall) && ( + ('max read gas') + : t('use estimated gas') + } + onChange={setWithEstimate} + value={withEstimate} + /> + )} + +
+ {t('{{executionTime}}s execution time', { replace: { executionTime: executionTime.toFixed(3) } })}{', '} + {t('{{percentage}}% of block weight', { replace: { percentage: percentage.toFixed(2) } })} +
+ } + ); } -const StyledDiv = styled.div` - .contracts--InputMegaGas-meter { - text-align: right; - } -`; - export default React.memo(InputMegaGas); diff --git a/packages/page-contracts/src/types.ts b/packages/page-contracts/src/types.ts index 1b41ba8675a5..5f0f72a4d8c6 100644 --- a/packages/page-contracts/src/types.ts +++ b/packages/page-contracts/src/types.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { Abi } from '@polkadot/api-contract'; +import type { WeightV2 } from '@polkadot/types/interfaces'; import type { BN } from '@polkadot/util'; export interface CodeJson { @@ -29,9 +30,15 @@ export interface UseWeight { executionTime: number; isEmpty: boolean; isValid: boolean; + isWeightV2: boolean; megaGas: BN; + megaRefTime: BN; + proofSize: BN; percentage: number; setIsEmpty: React.Dispatch setMegaGas: React.Dispatch; + setMegaRefTime: React.Dispatch; + setProofSize: React.Dispatch; weight: BN; + weightV2: WeightV2; } diff --git a/packages/page-contracts/src/useWeight.ts b/packages/page-contracts/src/useWeight.ts index bf0d19b983e8..5245cc3224d1 100644 --- a/packages/page-contracts/src/useWeight.ts +++ b/packages/page-contracts/src/useWeight.ts @@ -1,7 +1,7 @@ // Copyright 2017-2023 @polkadot/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { Weight } from '@polkadot/types/interfaces'; +import type { Weight, WeightV2 } from '@polkadot/types/interfaces'; import type { BN } from '@polkadot/util'; import type { UseWeight } from './types.js'; @@ -9,11 +9,12 @@ import { useCallback, useMemo, useState } from 'react'; import { createNamedHook, useApi, useBlockInterval } from '@polkadot/react-hooks'; import { convertWeight } from '@polkadot/react-hooks/useWeight'; -import { BN_MILLION, BN_TEN, BN_ZERO } from '@polkadot/util'; +import { BN_MILLION, BN_ONE, BN_TEN, BN_ZERO } from '@polkadot/util'; function useWeightImpl (): UseWeight { const { api } = useApi(); const blockTime = useBlockInterval(); + const isWeightV2 = !!api.registry.createType('Weight').proofSize; const [megaGas, _setMegaGas] = useState( convertWeight( api.consts.system.blockWeights @@ -21,6 +22,16 @@ function useWeightImpl (): UseWeight { : api.consts.system.maximumBlockWeight as Weight ).v1Weight.div(BN_MILLION).div(BN_TEN) ); + const [megaRefTime, _setMegaRefTime] = useState( + api.consts.system.blockWeights + ? api.consts.system.blockWeights.perClass.normal.maxExtrinsic.unwrapOrDefault().refTime.toBn().div(BN_MILLION).div(BN_TEN) + : BN_ZERO + ); + const [proofSize, _setProofSize] = useState( + api.consts.system.blockWeights + ? api.consts.system.blockWeights.perClass.normal.maxExtrinsic.unwrapOrDefault().proofSize.toBn() + : BN_ZERO + ); const [isEmpty, setIsEmpty] = useState(false); const setMegaGas = useCallback( @@ -32,11 +43,33 @@ function useWeightImpl (): UseWeight { ).v1Weight.div(BN_MILLION).div(BN_TEN)), [api] ); + const setMegaRefTime = useCallback( + (value?: BN | undefined) => + _setMegaRefTime( + value || api.consts.system.blockWeights + ? api.consts.system.blockWeights.perClass.normal.maxExtrinsic.unwrapOrDefault().refTime.toBn().div(BN_MILLION).div(BN_TEN) + : BN_ZERO + ), + [api] + ); + const setProofSize = useCallback( + (value?: BN | undefined) => + _setProofSize( + value || api.consts.system.blockWeights + ? api.consts.system.blockWeights.perClass.normal.maxExtrinsic.unwrapOrDefault().proofSize.toBn() + : BN_ZERO + ), + [api] + ); return useMemo((): UseWeight => { let executionTime = 0; let percentage = 0; let weight = BN_ZERO; + let weightV2 = api.registry.createType('WeightV2', { + proofSize: BN_ZERO, + refTime: BN_ZERO + }); let isValid = false; if (megaGas) { @@ -53,17 +86,40 @@ function useWeightImpl (): UseWeight { isValid = !megaGas.isZero() && percentage < 65; } + if (isWeightV2 && megaRefTime && proofSize) { + weightV2 = api.registry.createType('WeightV2', { + proofSize, + refTime: megaRefTime.mul(BN_MILLION) + }); + executionTime = megaRefTime.mul(BN_MILLION).mul(blockTime).div( + api.consts.system.blockWeights + ? api.consts.system.blockWeights.perClass.normal.maxExtrinsic.unwrapOrDefault().refTime.toBn() + : BN_ONE + ).toNumber(); + percentage = (executionTime / blockTime.toNumber()) * 100; + + // execution is 2s of 6s blocks, i.e. 1/3 + executionTime = executionTime / 3000; + isValid = !megaRefTime.isZero(); // && percentage < 65; + } + return { executionTime, isEmpty, isValid: isEmpty || isValid, + isWeightV2, megaGas: megaGas || BN_ZERO, + megaRefTime: megaRefTime || BN_ZERO, percentage, + proofSize: proofSize || BN_ZERO, setIsEmpty, setMegaGas, - weight + setMegaRefTime, + setProofSize, + weight, + weightV2 }; - }, [api, blockTime, isEmpty, megaGas, setIsEmpty, setMegaGas]); + }, [api, blockTime, isEmpty, isWeightV2, megaGas, megaRefTime, proofSize, setIsEmpty, setMegaGas, setMegaRefTime, setProofSize]); } export default createNamedHook('useWeight', useWeightImpl);