diff --git a/cypress/e2e/contracts/flipper.spec.ts b/cypress/e2e/contracts/flipper.spec.ts index fd4e451d..81a8ed27 100644 --- a/cypress/e2e/contracts/flipper.spec.ts +++ b/cypress/e2e/contracts/flipper.spec.ts @@ -10,6 +10,7 @@ import { assertInstantiate, assertReturnValue, selectMessage, + assertDryRun, } from '../../support/util'; describe('Flipper Contract ', () => { @@ -48,6 +49,7 @@ describe('Flipper Contract ', () => { }); it(`submits flip() transaction`, () => { selectMessage('flip', 0); + assertDryRun(); cy.contains('Call contract').click(); cy.get('[data-cy="transaction-complete"]', { timeout }) .should('be.visible') diff --git a/cypress/e2e/instantiateDryRun.ts b/cypress/e2e/instantiateDryRun.ts index 1fe33b35..c6aa51e1 100644 --- a/cypress/e2e/instantiateDryRun.ts +++ b/cypress/e2e/instantiateDryRun.ts @@ -37,7 +37,7 @@ describe('Instantiate dry run', () => { cy.get('[data-cy="estimated-storage-deposit"]') .should('not.contain', 'None') .and('not.be.empty'); - cy.get('[data-cy="estimated-gas"]').should('not.contain', 'None').and('not.be.empty'); + cy.get('[data-cy="dry-run-estimations"]').should('not.contain', 'None').and('not.be.empty'); cy.get('[data-cy="dry-run-account"]') .find('[data-cy="identicon"]') .should('have.lengthOf', 1); diff --git a/cypress/support/util.ts b/cypress/support/util.ts index c052e235..aea59f46 100644 --- a/cypress/support/util.ts +++ b/cypress/support/util.ts @@ -18,7 +18,9 @@ export function assertMoveToStep2() { cy.get('[data-cy="next-btn"]').click(); cy.contains('Deployment Constructor').scrollIntoView().should('be.visible'); cy.contains('Deployment Salt').scrollIntoView().should('be.visible'); - cy.contains('Max Gas Allowed').scrollIntoView().should('be.visible'); + cy.contains('RefTime Limit').scrollIntoView().should('be.visible'); + cy.contains('ProofSize Limit').scrollIntoView().should('be.visible'); + assertDryRun(); } export function assertMoveToStep3() { @@ -26,6 +28,13 @@ export function assertMoveToStep3() { cy.get('[data-cy="transaction-queued"]').should('be.visible'); } +export function assertDryRun() { + cy.get('[data-cy="dry-run-estimations"]', { timeout }) + .scrollIntoView() + .should('be.visible') + .and('contain', 'GasConsumed'); +} + export function assertContractRedirect() { cy.url().should('contain', '/contract/'); } @@ -45,6 +54,7 @@ export function assertInstantiate() { } export function assertCall() { + assertDryRun(); cy.contains('Call contract').click(); cy.get('[data-cy="transaction-complete"]', { timeout }) .should('be.visible') diff --git a/package.json b/package.json index 2c81d8f4..c6efdf8b 100644 --- a/package.json +++ b/package.json @@ -29,30 +29,30 @@ "author": "", "license": "ISC", "dependencies": { - "@headlessui/react": "^1.6.6", + "@headlessui/react": "^1.7.4", "@heroicons/react": "^1.0.6", - "@polkadot/api": "9.4.3", - "@polkadot/api-contract": "9.4.3", + "@polkadot/api": "9.8.2", + "@polkadot/api-contract": "9.8.2", "@polkadot/extension-dapp": "^0.44.6", - "@polkadot/ui-keyring": "^2.9.10", - "@polkadot/ui-shared": "^2.9.10", + "@polkadot/ui-keyring": "^2.9.13", + "@polkadot/ui-shared": "^2.9.13", "big.js": "^6.2.1", "buffer": "^6.0.3", - "copy-to-clipboard": "^3.3.2", - "date-fns": "^2.29.2", + "copy-to-clipboard": "^3.3.3", + "date-fns": "^2.29.3", "dexie": "^3.2.2", "dexie-react-hooks": "1.0.7", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-dropzone": "^14.2.2", + "react-dropzone": "^14.2.3", "react-markdown": "^8.0.3", - "react-router": "^6.3.0", - "react-router-dom": "^6.3.0", - "react-select": "^5.4.0", - "react-tooltip": "^4.2.21", + "react-router": "^6.4.3", + "react-router-dom": "^6.4.3", + "react-select": "^5.6.1", + "react-tooltip": "^4.5.0", "remark-gfm": "^3.0.1", - "styled-components": "^5.3.5", - "tailwind-merge": "^1.2.1", + "styled-components": "^5.3.6", + "tailwind-merge": "^1.8.0", "yup": "^0.32.11" }, "devDependencies": { @@ -62,39 +62,37 @@ "@nabla/vite-plugin-eslint": "^1.4.1", "@tailwindcss/forms": "^0.5.3", "@types/bcryptjs": "^2.4.2", - "@types/big.js": "^6.1.5", - "@types/node": "^18.7.1", - "@types/react-dom": "^18.0.6", - "@types/react-dropzone": "^5.1.0", - "@types/react-select": "^5.0.1", - "@typescript-eslint/eslint-plugin": "^5.36.1", - "@typescript-eslint/parser": "^5.36.1", - "@vitejs/plugin-react": "^2.1.0", - "autoprefixer": "^10.4.8", + "@types/big.js": "^6.1.6", + "@types/node": "^18.11.9", + "@types/react-dom": "^18.0.9", + "@typescript-eslint/eslint-plugin": "^5.43.0", + "@typescript-eslint/parser": "^5.43.0", + "@vitejs/plugin-react": "^2.2.0", + "autoprefixer": "^10.4.13", "cross-env": "^7.0.3", - "cypress": "^10.7.0", + "cypress": "^11.1.0", "cypress-file-upload": "^5.0.8", - "eslint": "^8.23.0", + "eslint": "^8.27.0", "eslint-config-prettier": "^8.5.0", - "eslint-import-resolver-typescript": "^3.5.0", + "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-react": "^7.31.6", + "eslint-plugin-react": "^7.31.10", "eslint-plugin-react-hooks": "^4.6.0", - "husky": "^8.0.1", + "husky": "^8.0.2", "istanbul-lib-coverage": "^3.2.0", "lint-staged": ">=13.0.3", "nyc": "^15.1.0", - "postcss": "^8.4.16", + "postcss": "^8.4.19", "postcss-import": "^15.0.0", "prettier": "^2.7.1", "source-map-support": "^0.5.21", - "tailwindcss": "^3.1.8", + "tailwindcss": "^3.2.4", "ts-node": "^10.9.1", - "typescript": "^4.8.2", - "vite": "^3.1.0", - "vite-plugin-istanbul": "^3.0.1", - "vite-tsconfig-paths": "^3.5.0" + "typescript": "^4.9.3", + "vite": "^3.2.4", + "vite-plugin-istanbul": "^3.0.2", + "vite-tsconfig-paths": "^3.5.2" }, "browserslist": [ "last 2 Chrome versions" diff --git a/src/helpers/instantiate/createInstantiateTx.ts b/src/helpers/instantiate/createInstantiateTx.ts index fd681a79..8552b6d3 100644 --- a/src/helpers/instantiate/createInstantiateTx.ts +++ b/src/helpers/instantiate/createInstantiateTx.ts @@ -11,7 +11,7 @@ export function createInstantiateTx( argValues, codeHash, constructorIndex, - weight: gasLimit, + gasLimit, value, metadata, salt, @@ -25,7 +25,7 @@ export function createInstantiateTx( const constructor = metadata.findConstructor(constructorIndex); const options = { - gasLimit, + gasLimit: api.registry.createType('WeightV2', gasLimit), salt: salt ? encodeSalt(salt) : null, storageDepositLimit: storageDepositLimit || undefined, value: value && constructor.isPayable ? api.registry.createType('Balance', value) : undefined, diff --git a/src/helpers/util/util.ts b/src/helpers/util/util.ts index 976e89e1..a76b4352 100644 --- a/src/helpers/util/util.ts +++ b/src/helpers/util/util.ts @@ -6,9 +6,8 @@ import { keyring } from '@polkadot/ui-keyring'; import format from 'date-fns/format'; import parseISO from 'date-fns/parseISO'; import { twMerge } from 'tailwind-merge'; -import { MAX_CALL_WEIGHT } from '../../constants'; import { BN_TEN } from './bn'; -import { ApiPromise, AbiParam, Registry, OrFalsy, Weight, CodeBundleDocument } from 'types'; +import { ApiPromise, AbiParam, Registry, CodeBundleDocument } from 'types'; export function classes(...classLists: (string | null | undefined | false)[]) { return twMerge(...classLists.map(classList => (!classList ? null : classList))); @@ -34,12 +33,6 @@ export function isEmptyObj(value: unknown) { return JSON.stringify(value) === '{}'; } -export function maximumBlockWeight(api: OrFalsy): Weight { - return api?.consts.system.blockWeights - ? api.consts.system.blockWeights.maxBlock - : (api?.consts.system.maximumBlockWeight as Weight) || MAX_CALL_WEIGHT; -} - export function randomAsU8a(length = 32) { return crypto.getRandomValues(new Uint8Array(length)); } diff --git a/src/types/substrate.ts b/src/types/substrate.ts index dad63f13..f9d19caf 100644 --- a/src/types/substrate.ts +++ b/src/types/substrate.ts @@ -8,8 +8,10 @@ export type { DispatchError, EventRecord, Weight, + WeightV2, ChainType, Hash, + ContractExecResult, } from '@polkadot/types/interfaces'; export type { KeyringPair } from '@polkadot/keyring/types'; export type { @@ -27,4 +29,5 @@ export { Bytes, Raw, TypeDefInfo } from '@polkadot/types'; export { Keyring } from '@polkadot/ui-keyring'; export { Abi, ContractPromise, BlueprintPromise } from '@polkadot/api-contract'; export { BlueprintSubmittableResult, CodeSubmittableResult } from '@polkadot/api-contract/base'; +export { ContractSubmittableResult } from '@polkadot/api-contract/base/Contract'; export { ApiPromise, SubmittableResult } from '@polkadot/api'; diff --git a/src/types/ui/contexts.ts b/src/types/ui/contexts.ts index d840de92..d2ae8232 100644 --- a/src/types/ui/contexts.ts +++ b/src/types/ui/contexts.ts @@ -10,6 +10,7 @@ import type { SubmittableExtrinsic, SubmittableResult, ChainType, + WeightV2, } from '../substrate'; import type { BN } from './util'; @@ -43,7 +44,7 @@ export interface InstantiateData { constructorIndex: number; salt?: string; storageDepositLimit?: BN; - weight: BN; + gasLimit: WeightV2 | null; codeHash?: string; } diff --git a/src/types/ui/hooks.ts b/src/types/ui/hooks.ts index 1033c558..dde370d8 100644 --- a/src/types/ui/hooks.ts +++ b/src/types/ui/hooks.ts @@ -7,7 +7,6 @@ import { BN, FileState, MetadataState, Validation } from './util'; export type InputMode = 'estimation' | 'custom'; export type UIGas = { - max: BN; isValid: boolean; setIsValid: (value: boolean) => void; limit: BN; @@ -16,6 +15,8 @@ export type UIGas = { setMode: (m: InputMode) => void; errorMsg: string; setErrorMsg: (m: string) => void; + text: string; + setText: (m: string) => void; }; export interface ValidFormField extends Validation { diff --git a/src/ui/components/common/NoticeBanner.tsx b/src/ui/components/common/NoticeBanner.tsx new file mode 100644 index 00000000..ae1f5379 --- /dev/null +++ b/src/ui/components/common/NoticeBanner.tsx @@ -0,0 +1,26 @@ +// Copyright 2022 @paritytech/contracts-ui authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { EmojiSadIcon } from '@heroicons/react/outline'; + +export function NoticeBanner({ isVisible, endpoint }: { isVisible: boolean; endpoint: string }) { + return isVisible ? ( +
+ + +

Unsuported node version.

+

+ Looks like your node does not support WeigthV2. +

+

+ Upgrade your node or use an{' '} + + older version of Contracts UI. + +

+
+ ) : null; +} diff --git a/src/ui/components/common/index.ts b/src/ui/components/common/index.ts index fba1e59a..25565e91 100644 --- a/src/ui/components/common/index.ts +++ b/src/ui/components/common/index.ts @@ -11,3 +11,4 @@ export * from './Switch'; export * from './Tabs'; export * from './SearchResults'; export * from './ConnectionError'; +export * from './NoticeBanner'; diff --git a/src/ui/components/contract/DryRunResult.tsx b/src/ui/components/contract/DryRunResult.tsx index 99aea2fd..44e67882 100644 --- a/src/ui/components/contract/DryRunResult.tsx +++ b/src/ui/components/contract/DryRunResult.tsx @@ -1,24 +1,33 @@ // Copyright 2022 @paritytech/contracts-ui authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import { AbiMessage, ContractCallOutcome } from '@polkadot/api-contract/types'; +// temporarily disabled until polkadot-js types `ContractCallOutcome` and `ContractExecResult` transition to WeightV2 +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +import { AbiMessage } from '@polkadot/api-contract/types'; import { DryRunError } from './DryRunError'; import { OutcomeItem } from './OutcomeItem'; import { useDecodedOutput } from 'ui/hooks'; import { classes, decodeStorageDeposit } from 'helpers'; +import { ContractExecResult, Registry } from 'types'; import { useApi } from 'ui/contexts'; interface Props { - outcome: ContractCallOutcome; + outcome: ContractExecResult; message: AbiMessage; + registry: Registry; } export function DryRunResult({ - outcome: { output, gasRequired, gasConsumed, storageDeposit, debugMessage, result }, + outcome: { gasRequired, gasConsumed, storageDeposit, debugMessage, result }, message, + registry, }: Props) { + const { decodedOutput, isError } = useDecodedOutput(result, message, registry); const { api } = useApi(); - const { decodedOutput, isError } = useDecodedOutput(output); const { storageDepositValue, storageDepositType } = decodeStorageDeposit(storageDeposit); const isDispatchable = message.isMutating || message.isPayable; @@ -26,8 +35,8 @@ export function DryRunResult({ result.isErr && result.asErr.isModule ? api.registry.findMetaError(result.asErr.asModule) : undefined; - - const shouldDisplayRequired = !gasConsumed.eq(gasRequired); + // @ts-ignore + const shouldDisplayRequired = !gasConsumed.refTime.toBn().eq(gasRequired.refTime.toBn()); const prediction = result.isErr ? 'Contract Reverted!' : isError @@ -66,23 +75,53 @@ export function DryRunResult({ )} {isDispatchable && ( - <> - +
+ GasConsumed +
+
+ +
+
+ +
+
+ {shouldDisplayRequired && ( - + <> + GasRequired +
+
+ +
+
+ +
+
+ )} - +
)} diff --git a/src/ui/components/contract/Interact.tsx b/src/ui/components/contract/Interact.tsx index f68f76db..cf3bead9 100644 --- a/src/ui/components/contract/Interact.tsx +++ b/src/ui/components/contract/Interact.tsx @@ -1,25 +1,29 @@ // Copyright 2022 @paritytech/contracts-ui authors & contributors // SPDX-License-Identifier: GPL-3.0-only +// temporarily disabled until polkadot-js types `ContractCallOutcome` and `ContractExecResult` transition to WeightV2 +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + import { useEffect, useState, useRef, useMemo } from 'react'; -import { ContractSubmittableResult } from '@polkadot/api-contract/base/Contract'; -import { AbiMessage, ContractCallOutcome } from '@polkadot/api-contract/types'; import { ResultsOutput } from './ResultsOutput'; +import { + AbiMessage, + ContractExecResult, + ContractSubmittableResult, + CallResult, + ContractPromise, + SubmittableResult, +} from 'types'; import { AccountSelect } from 'ui/components/account'; import { Dropdown, Button, Buttons } from 'ui/components/common'; -import { - ArgumentForm, - InputGas, - InputBalance, - InputStorageDepositLimit, - Form, - FormField, -} from 'ui/components/form'; +import { ArgumentForm, Form, FormField, OptionsForm } from 'ui/components/form'; import { transformUserInput, BN_ZERO } from 'helpers'; import { useApi, useTransactions } from 'ui/contexts'; -import { CallResult, ContractPromise, SubmittableResult } from 'types'; -import { useGas, useBalance, useArgValues } from 'ui/hooks'; -import { useToggle } from 'ui/hooks/useToggle'; +import { useWeight, useBalance, useArgValues } from 'ui/hooks'; import { useStorageDepositLimit } from 'ui/hooks/useStorageDepositLimit'; import { createMessageOptions } from 'ui/util/dropdown'; @@ -27,24 +31,35 @@ interface Props { contract: ContractPromise; } -export const InteractTab = ({ contract: { abi, query, registry, tx, address } }: Props) => { +export const InteractTab = ({ + contract: { + abi, + abi: { registry }, + query, + tx, + address, + }, +}: Props) => { const { accounts, api } = useApi(); const { queue, process, txs } = useTransactions(); const [message, setMessage] = useState(); const [argValues, setArgValues] = useArgValues(message?.args || [], registry); const [callResults, setCallResults] = useState([]); - const { value, onChange: setValue, ...valueValidation } = useBalance(BN_ZERO); + const valueState = useBalance(BN_ZERO); + const { value } = valueState; const [accountId, setAccountId] = useState(''); const [txId, setTxId] = useState(0); const [nextResultId, setNextResultId] = useState(1); - const [isUsingStorageDepositLimit, toggleIsUsingStorageDepositLimit] = useToggle(); - const [outcome, setOutcome] = useState(); + const [outcome, setOutcome] = useState(); const storageDepositLimit = useStorageDepositLimit(accountId); - const gas = useGas(outcome?.gasRequired); + //@ts-ignore + const refTime = useWeight(outcome?.gasRequired.refTime.toBn()); + //@ts-ignore + const proofSize = useWeight(outcome?.gasRequired.proofSize.toBn()); const timeoutId = useRef(null); const inputData = useMemo(() => { - return message ? transformUserInput(registry, message.args, argValues) : []; + return message?.toU8a(transformUserInput(registry, message.args, argValues)); }, [argValues, registry, message]); useEffect((): void => { @@ -57,17 +72,30 @@ export const InteractTab = ({ contract: { abi, query, registry, tx, address } }: setOutcome(undefined); // todo: call results storage setCallResults([]); - }, [abi.messages]); + }, [abi.messages, setArgValues, address]); useEffect((): void => { async function dryRun() { if (!message || typeof query[message.method] !== 'function') return; const options = { - gasLimit: gas.mode === 'custom' ? (gas.limit.isZero() ? gas.limit.addn(1) : gas.limit) : -1, - storageDepositLimit: isUsingStorageDepositLimit ? storageDepositLimit.value : undefined, + gasLimit: + refTime.mode === 'custom' || proofSize.mode === 'custom' + ? api.registry.createType('WeightV2', { + refTime: refTime.limit, + proofSize: proofSize.limit, + }) + : null, + storageDepositLimit: storageDepositLimit.isActive ? storageDepositLimit.value : null, value: message?.isPayable ? value : undefined, }; - const o = await query[message.method](accountId, options, ...inputData); + const o = await api.call.contractsApi.call( + accountId, + address, + options.value ?? BN_ZERO, + options.gasLimit, + options.storageDepositLimit, + inputData ?? '' + ); setOutcome(o); } @@ -83,13 +111,18 @@ export const InteractTab = ({ contract: { abi, query, registry, tx, address } }: }, [ accountId, query, - isUsingStorageDepositLimit, message, inputData, storageDepositLimit.value, - gas.limit, - gas.mode, + refTime.limit, + refTime.mode, value, + api.registry, + api.call.contractsApi, + address, + proofSize.limit, + proofSize.mode, + storageDepositLimit.isActive, ]); useEffect(() => { @@ -124,8 +157,14 @@ export const InteractTab = ({ contract: { abi, query, registry, tx, address } }: const { storageDeposit, gasRequired } = outcome ?? {}; const options = { - gasLimit: gas.mode === 'custom' ? gas.limit : gasRequired, - storageDepositLimit: isUsingStorageDepositLimit + gasLimit: + refTime.mode === 'custom' || proofSize.mode === 'custom' + ? api.registry.createType('WeightV2', { + refTime: refTime.limit, + proofSize: proofSize.limit, + }) + : gasRequired, + storageDepositLimit: storageDepositLimit.isActive ? storageDepositLimit.value : storageDeposit?.isCharge ? !storageDeposit?.asCharge.eq(BN_ZERO) @@ -137,7 +176,9 @@ export const InteractTab = ({ contract: { abi, query, registry, tx, address } }: const isValid = (result: SubmittableResult) => !result.isError && !result.dispatchError; - const extrinsic = message && tx[message.method](options, ...inputData); + const extrinsic = + message && + tx[message.method](options, ...transformUserInput(registry, message.args, argValues)); if (extrinsic && accountId) { newId.current = queue({ @@ -150,10 +191,11 @@ export const InteractTab = ({ contract: { abi, query, registry, tx, address } }: } }; - const decodedOutput = outcome?.output?.toHuman(); + const decodedOutput = outcome?.result?.toHuman(); const callDisabled = - !gas.isValid || + !refTime.isValid || + !proofSize.isValid || txs[txId]?.status === 'processing' || !!outcome?.result.isErr || (!!decodedOutput && typeof decodedOutput === 'object' && 'Err' in decodedOutput); @@ -204,47 +246,14 @@ export const InteractTab = ({ contract: { abi, query, registry, tx, address } }: )} - {message?.isPayable && ( - - - - )} {isDispatchable && ( -
- - - - - - -
+ )} diff --git a/src/ui/components/contract/ResultsOutput.tsx b/src/ui/components/contract/ResultsOutput.tsx index 2b315a65..e191d4f3 100644 --- a/src/ui/components/contract/ResultsOutput.tsx +++ b/src/ui/components/contract/ResultsOutput.tsx @@ -1,17 +1,16 @@ // Copyright 2022 @paritytech/contracts-ui authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import { AbiMessage, ContractCallOutcome } from '@polkadot/api-contract/types'; import { SidePanel } from '../common/SidePanel'; import { TransactionResult } from './TransactionResult'; import { DryRunResult } from './DryRunResult'; -import { CallResult, Registry } from 'types'; +import { CallResult, ContractExecResult, Registry, AbiMessage } from 'types'; interface Props { results: CallResult[]; registry: Registry; message: AbiMessage; - outcome?: ContractCallOutcome; + outcome?: ContractExecResult; } export const ResultsOutput = ({ registry, results, outcome, message }: Props) => { @@ -22,7 +21,7 @@ export const ResultsOutput = ({ registry, results, outcome, message }: Props) => emptyView="No results yet." >
- {outcome && } + {outcome && }
diff --git a/src/ui/components/form/InputBn.tsx b/src/ui/components/form/InputBn.tsx index a65f5d9f..740c327e 100644 --- a/src/ui/components/form/InputBn.tsx +++ b/src/ui/components/form/InputBn.tsx @@ -29,7 +29,7 @@ export function InputBn({ onChange, typeDef }: Props): JSX.Element { value={displayValue} onChange={handleChage} placeholder="Input a number" - data-cy="gas-input" + data-cy="bn-input" min={min} /> diff --git a/src/ui/components/form/InputStorageDepositLimit.tsx b/src/ui/components/form/InputStorageDepositLimit.tsx index a02fea19..91356ccc 100644 --- a/src/ui/components/form/InputStorageDepositLimit.tsx +++ b/src/ui/components/form/InputStorageDepositLimit.tsx @@ -39,7 +39,7 @@ export function InputStorageDepositLimit({
{ - if (mode === 'estimation' && estimatedWeight) { - setDisplayValue(estimatedWeight.toString()); - if (limit.eq(BN_ZERO)) setLimit(estimatedWeight); - } - }, [estimatedWeight, limit, mode, setLimit]); - + name, + text, + setText, +}: UIGas & { name: string }) { return ( -
+ <> { if (mode === 'custom') { const bn = new BN(e.target.value); - if (bn.lte(max)) { - setDisplayValue(e.target.value); - setLimit(bn); - setErrorMsg(''); - setIsValid(true); + if (bn.lte(MAX_CALL_WEIGHT)) { + if (!bn.eq(limit)) { + setText(e.target.value); + setLimit(bn); + setErrorMsg(''); + setIsValid(true); + } } else { setErrorMsg('Value exceeds maximum block weight'); setIsValid(false); @@ -47,9 +40,8 @@ export function InputGas({ } }} placeholder="MGas" - data-cy="gas-input" + data-cy="refTime-input" min="0" - max={max.toString()} className="disabled:opacity-60" /> - Use Estimated Gas + {`Use Estimation`} ) : ( <> - {'Using Estimated Gas'} + {`Using Estimation`}  {' ยท '}  { e.preventDefault(); setMode('custom'); - estimatedWeight && setLimit(estimatedWeight); }} className="text-green-500" - data-cy="use-custom-gas" + data-cy="use-custom-refTime" > Use Custom @@ -87,6 +78,6 @@ export function InputGas({ } withAccessory={true} /> -
+ ); } diff --git a/src/ui/components/form/OptionsForm.tsx b/src/ui/components/form/OptionsForm.tsx new file mode 100644 index 00000000..982a797f --- /dev/null +++ b/src/ui/components/form/OptionsForm.tsx @@ -0,0 +1,80 @@ +// Copyright 2022 @paritytech/contracts-ui authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { UIGas, UseBalance } from 'types'; + +import { InputWeight } from 'ui/components/form/InputWeight'; +import { InputBalance } from 'ui/components/form/InputBalance'; +import { InputStorageDepositLimit } from 'ui/components/form/InputStorageDepositLimit'; +import { FormField } from 'ui/components/form/FormField'; +import { UseStorageDepositLimit } from 'ui/hooks/useStorageDepositLimit'; + +interface Props { + isPayable: boolean; + refTime: UIGas; + proofSize: UIGas; + storageDepositLimit: UseStorageDepositLimit; + value: UseBalance; +} + +export function OptionsForm({ + isPayable, + refTime, + proofSize, + storageDepositLimit, + value: { value, onChange: setValue, ...valueValidation }, +}: Props) { + return ( + <> +
+ + + + + + +
+
+ + + + {isPayable && ( + + + + )} +
+ + ); +} diff --git a/src/ui/components/form/index.ts b/src/ui/components/form/index.ts index e8b5139c..c8a225fa 100644 --- a/src/ui/components/form/index.ts +++ b/src/ui/components/form/index.ts @@ -9,7 +9,8 @@ export * from './InputBn'; export * from './InputFile'; export * from './InputNumber'; export * from './InputSalt'; -export * from './InputGas'; +export * from './InputWeight'; export * from './InputStorageDepositLimit'; export * from './ArgumentForm'; +export * from './OptionsForm'; export * from './Bool'; diff --git a/src/ui/components/instantiate/DryRun.tsx b/src/ui/components/instantiate/DryRun.tsx index 7e75b2da..0f689372 100644 --- a/src/ui/components/instantiate/DryRun.tsx +++ b/src/ui/components/instantiate/DryRun.tsx @@ -4,10 +4,14 @@ import { CheckCircleIcon, ExclamationCircleIcon } from '@heroicons/react/outline'; import { SidePanel } from '../common/SidePanel'; import { Account } from '../account/Account'; +import { OutcomeItem } from '../contract/OutcomeItem'; import { useApi, useInstantiate } from 'ui/contexts'; export function DryRun() { - const { dryRunResult } = useInstantiate(); + const { + dryRunResult, + data: { constructorIndex }, + } = useInstantiate(); const { api } = useApi(); const dryRunError = dryRunResult?.result.isErr && dryRunResult?.result.asErr.isModule @@ -15,18 +19,55 @@ export function DryRun() { : null; return ( - +
-
-
Gas Required:
-
- {dryRunResult?.gasRequired && <>{dryRunResult.gasRequired.toHuman()}} +
+
+ {dryRunResult?.gasConsumed && ( + <> + GasConsumed +
+
+ +
+
+ +
+
+ + )} + {dryRunResult?.gasRequired && ( + <> + GasRequired +
+
+ +
+
+ +
+
+ + )}
-
-
Gas Consumed:
-
{dryRunResult?.gasConsumed && <>{dryRunResult.gasConsumed.toHuman()}}
-
+
Storage Deposit:
diff --git a/src/ui/components/instantiate/Step2.tsx b/src/ui/components/instantiate/Step2.tsx index 24c80f0b..5ec7849b 100644 --- a/src/ui/components/instantiate/Step2.tsx +++ b/src/ui/components/instantiate/Step2.tsx @@ -5,10 +5,8 @@ import { useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router'; import { Button, Buttons } from '../common/Button'; import { Form, FormField, getValidation } from '../form/FormField'; -import { InputBalance } from '../form/InputBalance'; import { InputSalt } from '../form/InputSalt'; -import { InputGas } from '../form/InputGas'; -import { InputStorageDepositLimit } from '../form/InputStorageDepositLimit'; +import { OptionsForm } from '../form'; import { isNumber, genRanHex, transformUserInput, BN_ZERO } from 'helpers'; import { ArgumentForm } from 'ui/components/form/ArgumentForm'; import { Dropdown } from 'ui/components/common/Dropdown'; @@ -17,7 +15,7 @@ import { useApi, useInstantiate } from 'ui/contexts'; import { useBalance } from 'ui/hooks/useBalance'; import { useArgValues } from 'ui/hooks/useArgValues'; import { useFormField } from 'ui/hooks/useFormField'; -import { useGas } from 'ui/hooks/useGas'; +import { useWeight } from 'ui/hooks/useWeight'; import { useToggle } from 'ui/hooks/useToggle'; import { AbiMessage, OrFalsy } from 'types'; import { useStorageDepositLimit } from 'ui/hooks/useStorageDepositLimit'; @@ -36,8 +34,10 @@ export function Step2() { const { accountId, metadata } = data; const [constructorIndex, setConstructorIndex] = useState(0); const [deployConstructor, setDeployConstructor] = useState(); - const { value, onChange: onChangeValue, ...valueValidation } = useBalance(BN_ZERO); - const gas = useGas(dryRunResult?.gasRequired); + const valueState = useBalance(BN_ZERO); + const { value } = valueState; + const refTime = useWeight(dryRunResult?.gasRequired.refTime.toBn()); + const proofSize = useWeight(dryRunResult?.gasRequired.proofSize.toBn()); const storageDepositLimit = useStorageDepositLimit(accountId); const salt = useFormField(genRanHex(64), validateSalt); const [argValues, setArgValues] = useArgValues(deployConstructor?.args ?? [], metadata?.registry); @@ -49,38 +49,44 @@ export function Step2() { }, [metadata, setConstructorIndex]); const [isUsingSalt, toggleIsUsingSalt] = useToggle(true); - const [isUsingStorageDepositLimit, toggleIsUsingStorageDepositLimit] = useToggle(); const params = useMemo(() => { const inputData = deployConstructor?.toU8a( transformUserInput(api.registry, deployConstructor.args, argValues) ); - return { origin: accountId, value: deployConstructor?.isPayable ? value : BN_ZERO, - gasLimit: gas.mode === 'custom' ? gas.limit : gas.max, - storageDepositLimit: isUsingStorageDepositLimit ? storageDepositLimit.value : undefined, + gasLimit: + refTime.mode === 'custom' || proofSize.mode === 'custom' + ? api.registry.createType('WeightV2', { + refTime: refTime.limit, + proofSize: proofSize.limit, + }) + : null, + storageDepositLimit: storageDepositLimit.isActive ? storageDepositLimit.value : undefined, code: codeHashUrlParam ? { Existing: codeHashUrlParam } : { Upload: metadata?.info.source.wasm }, data: inputData, - salt: salt.value ?? null, + salt: isUsingSalt ? salt.value : undefined, }; }, [ - accountId, + deployConstructor, api.registry, argValues, + accountId, + value, + refTime.mode, + refTime.limit, + proofSize.mode, + proofSize.limit, + storageDepositLimit.isActive, + storageDepositLimit.value, codeHashUrlParam, - deployConstructor, - isUsingStorageDepositLimit, metadata?.info.source.wasm, + isUsingSalt, salt.value, - storageDepositLimit.value, - value, - gas.max, - gas.limit, - gas.mode, ]); useEffect((): void => { @@ -93,31 +99,37 @@ export function Step2() { params.storageDepositLimit ?? null, params.code, params.data ?? '', - params.salt + params.salt ?? '' ); - setDryRunResult(result); + + if (JSON.stringify(dryRunResult) !== JSON.stringify(result)) { + setDryRunResult(result); + } } catch (e) { console.error(e); } } dryRun().catch(e => console.error(e)); - }, [api.call.contractsApi, params, setDryRunResult]); + }, [api.call.contractsApi, dryRunResult, params, setDryRunResult]); const onSubmit = () => { - const { salt, storageDepositLimit, value } = params; - const { storageDeposit, gasRequired } = dryRunResult ?? {}; + const { salt, value, gasLimit } = params; + const { storageDeposit } = dryRunResult ?? {}; setData({ ...data, constructorIndex, salt, value, argValues, - storageDepositLimit: isUsingStorageDepositLimit - ? storageDepositLimit + storageDepositLimit: storageDepositLimit.isActive + ? storageDepositLimit.value : storageDeposit?.isCharge ? storageDeposit?.asCharge : undefined, - weight: gas.mode === 'custom' ? gas.limit : gasRequired ?? gas.max, + gasLimit: + refTime.mode === 'custom' || proofSize.mode === 'custom' + ? gasLimit + : dryRunResult?.gasRequired ?? null, }); setStep(3); }; @@ -157,16 +169,6 @@ export function Step2() { /> )} - {deployConstructor?.isPayable && ( - - - - )} - -
- - - - - - -
+