From c2543ae9b2a90f8887ceda09fa2d0c8e06f0675d Mon Sep 17 00:00:00 2001 From: Keith Ingram Date: Thu, 31 Oct 2019 20:45:31 +0200 Subject: [PATCH] Contract.call RPC interface with return type (#1780) * contracts api + rpc * finalized * tweak * api bump * linting --- package.json | 6 +- packages/app-contracts/package.json | 2 +- packages/app-contracts/src/Contracts/Call.tsx | 302 +++++++++--------- .../app-contracts/src/Contracts/Contract.tsx | 22 +- .../app-contracts/src/Contracts/Outcome.tsx | 103 ++++++ .../app-contracts/src/Contracts/index.tsx | 86 +++-- packages/app-contracts/src/Contracts/util.tsx | 37 +-- packages/react-api/package.json | 2 +- packages/react-components/src/AddressInfo.tsx | 4 +- packages/react-components/src/CopyButton.tsx | 4 +- packages/react-components/src/IconLink.tsx | 40 +++ .../react-components/src/MessageSignature.tsx | 15 +- packages/react-components/src/Messages.tsx | 19 +- packages/react-components/src/Row.tsx | 2 +- packages/react-components/src/index.tsx | 1 + .../react-components/src/styles/components.ts | 7 +- yarn.lock | 90 +++--- 17 files changed, 458 insertions(+), 284 deletions(-) create mode 100644 packages/app-contracts/src/Contracts/Outcome.tsx create mode 100644 packages/react-components/src/IconLink.tsx diff --git a/package.json b/package.json index 821b6190b4..bfb700c67c 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "packages/*" ], "resolutions": { - "@polkadot/api": "^0.96.0-beta.16", - "@polkadot/api-contract": "^0.96.0-beta.16", + "@polkadot/api": "^0.96.0-beta.17", + "@polkadot/api-contract": "^0.96.0-beta.17", "@polkadot/keyring": "^1.6.1", - "@polkadot/types": "^0.96.0-beta.16", + "@polkadot/types": "^0.96.0-beta.17", "@polkadot/util": "^1.6.1", "@polkadot/util-crypto": "^1.6.1", "babel-core": "^7.0.0-bridge.0", diff --git a/packages/app-contracts/package.json b/packages/app-contracts/package.json index 0bc2830f64..88508a5747 100644 --- a/packages/app-contracts/package.json +++ b/packages/app-contracts/package.json @@ -11,7 +11,7 @@ "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.6.3", - "@polkadot/api-contract": "^0.96.0-beta.16", + "@polkadot/api-contract": "^0.96.0-beta.17", "@polkadot/react-components": "^0.37.0-beta.28" } } diff --git a/packages/app-contracts/src/Contracts/Call.tsx b/packages/app-contracts/src/Contracts/Call.tsx index 6125118e4f..32ed0fa67e 100644 --- a/packages/app-contracts/src/Contracts/Call.tsx +++ b/packages/app-contracts/src/Contracts/Call.tsx @@ -2,63 +2,76 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { ContractCallOutcome } from '@polkadot/api-contract/types'; import { ApiProps } from '@polkadot/react-api/types'; -import { BareProps, CallContract, I18nProps, StringOrNull } from '@polkadot/react-components/types'; -import { QueueProps } from '@polkadot/react-components/Status/types'; +import { BareProps, I18nProps, StringOrNull } from '@polkadot/react-components/types'; import { ContractExecResult } from '@polkadot/types/interfaces/contracts'; import BN from 'bn.js'; -import React, { useState } from 'react'; -import rpc from '@polkadot/jsonrpc'; -import { Button, Dropdown, InputAddress, InputBalance, InputNumber, Modal, Output, TxButton } from '@polkadot/react-components'; -import { QueueConsumer } from '@polkadot/react-components/Status/Context'; +import React, { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { Button, Dropdown, IconLink, InputAddress, InputBalance, InputNumber, Modal, Toggle, TxButton } from '@polkadot/react-components'; +import { PromiseContract as ApiContract } from '@polkadot/api-contract'; import { withApi, withMulti } from '@polkadot/react-api'; -import { isNull, isUndefined } from '@polkadot/util'; +import { createValue } from '@polkadot/react-params/values'; +import { isNull } from '@polkadot/util'; import Params from '../Params'; +import Outcome from './Outcome'; import translate from '../translate'; import { GAS_LIMIT } from '../constants'; -import { findCallMethod, getContractForAddress, getCallMethodOptions, getContractMethodFn } from './util'; +import { getCallMessageOptions } from './util'; interface Props extends BareProps, I18nProps, ApiProps { - callContract: CallContract | null; - callMethodIndex: number | null; + callContract: ApiContract | null; + callMessageIndex: number | null; + callResults: ContractExecResult[]; isOpen: boolean; - onChangeCallContract: (callContract: CallContract) => void; - onChangeCallMethodIndex: (callMethodIndex: number) => void; + onChangeCallContractAddress: (callContractAddress: StringOrNull) => void; + onChangeCallMessageIndex: (callMessageIndex: number) => void; onClose: () => void; } function Call (props: Props): React.ReactElement | null { - const { isOpen, callContract, callMethodIndex, onChangeCallContract, onChangeCallMethodIndex, onClose, api, t } = props; + const { className, isOpen, callContract, callMessageIndex, onChangeCallContractAddress, onChangeCallMessageIndex, onClose, api, t } = props; - if (isNull(callContract) || isNull(callMethodIndex)) { + if (isNull(callContract) || isNull(callMessageIndex)) { return null; } const hasRpc = api.rpc.contracts && api.rpc.contracts.call; - const callMethod = findCallMethod(callContract, callMethodIndex); - const useRpc = hasRpc && callMethod && !callMethod.mutates; - // const isRpc = false; + let callMessage = callContract.getMessage(callMessageIndex); const [accountId, setAccountId] = useState(null); const [endowment, setEndowment] = useState(new BN(0)); const [gasLimit, setGasLimit] = useState(new BN(GAS_LIMIT)); const [isBusy, setIsBusy] = useState(false); - const [params, setParams] = useState([]); + const [outcomes, setOutcomes] = useState([]); + const [params, setParams] = useState(callMessage ? callMessage.def.args.map(({ type }): any => createValue({ type })) : []); + const [useRpc, setUseRpc] = useState(callMessage && !callMessage.def.mutates); + + useEffect((): void => { + callMessage = callContract.getMessage(callMessageIndex); + + setParams(callMessage ? callMessage.def.args.map(({ type }): any => createValue({ type })) : []); + if (!callMessage || callMessage.def.mutates) { + setUseRpc(false); + } else { + setUseRpc(true); + } + }, [callContract, callMessageIndex]); + + useEffect((): void => { + setOutcomes([]); + }, [callContract]); const _onChangeAccountId = (accountId: StringOrNull): void => setAccountId(accountId); - const _onChangeCallAddress = (callAddress: StringOrNull): void => { - const callContract = getContractForAddress(callAddress); - - onChangeCallContract && callContract.abi && onChangeCallContract(callContract); - }; - - const _onChangeCallMethodString = (callMethodString: string): void => { - setParams([]); - onChangeCallMethodIndex && onChangeCallMethodIndex(parseInt(callMethodString, 10) || 0); + const _onChangeCallMessageIndexString = (callMessageIndexString: string): void => { + onChangeCallMessageIndex && onChangeCallMessageIndex( + parseInt(callMessageIndexString, 10) || 0 + ); }; const _onChangeEndowment = (endowment?: BN): void => endowment && setEndowment(endowment); @@ -68,28 +81,29 @@ function Call (props: Props): React.ReactElement | null { const _toggleBusy = (): void => setIsBusy(!isBusy); const _constructTx = (): any[] => { - const fn = getContractMethodFn(callContract, callMethod); - if (!fn || !callContract || !callContract.address) { + if (!accountId || !callMessage || !callMessage.fn || !callContract || !callContract.address) { return []; } - return [callContract.address, endowment, gasLimit, fn(...params)]; + return [callContract.address.toString(), endowment, gasLimit, callMessage.fn(...params)]; }; - const _constructRpc = (): [any] | null => { - const fn = getContractMethodFn(callContract, callMethod); - if (!fn || !accountId || !callContract || !callContract.address || !callContract.abi || !callMethod) { - return null; - } - return [ - { - origin: accountId, - dest: callContract.address, - value: endowment, - gasLimit, - inputData: fn(...params) - } - ]; + const _onSubmitRpc = (): void => { + if (!accountId) return; + + callContract + .call('rpc', callMessage.def.name, endowment, gasLimit, ...params) + .send(accountId) + .then( + (outcome: ContractCallOutcome): void => { + setOutcomes([outcome, ...outcomes]); + } + ); + }; + + const _onClearOutcomes = (): void => setOutcomes([]); + const _onClearOutcome = (outcomeIndex: number) => (): void => { + setOutcomes(outcomes.slice(0, outcomeIndex).concat(outcomes.slice(outcomeIndex + 1))); }; const isEndowmentValid = true; @@ -98,7 +112,7 @@ function Call (props: Props): React.ReactElement | null { return ( | null { help={t('A deployed contract that has either been deployed or attached. The address and ABI are used to construct the parameters.')} isDisabled={isBusy} label={t('contract to use')} - onChange={_onChangeCallAddress} + onChange={onChangeCallContractAddress} type='contract' - value={callContract.address} + value={callContract.address.toString()} /> - {callMethodIndex !== null && ( + {callMessageIndex !== null && ( <> @@ -167,109 +182,98 @@ function Call (props: Props): React.ReactElement | null { /> )} - - { - ({ queueRpc, txqueue }: QueueProps): React.ReactNode => { - const _onSubmitRpc = (): void => { - const values = _constructRpc(); - - if (values) { - queueRpc({ - accountId, - rpc: rpc.contracts.methods.call, - values - }); - } - }; - - const results = txqueue - .filter(({ error, result, rpc, values }): boolean => - ((!isUndefined(error) || !isUndefined(result)) && - rpc.section === 'contracts' && rpc.method === 'call' && !!values && values[0].dest === callContract.address) - ) - .reverse(); - - return ( - <> - -