Add storage deposit support in apps (#6657)

* add basic storage deposit input

* prefill input with free balance

* make value zeroable

* changes to deploy modal

* support older nodes

* storage deposit support in call

* remove input and pass null

* remove unused

* properly check if value input shoudl be zeroable

* remove unused

* hide input when constructor is not payable

* update api

* little cleanup

* remove unnecessary check
This commit is contained in:
Andreea Eftene 2021-12-22 10:03:22 +01:00 committed by GitHub
parent 8a0b974374
commit c2be422be0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 62 additions and 66 deletions

View File

@ -8,12 +8,11 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { CodePromise } from '@polkadot/api-contract';
import { Button, Dropdown, InputAddress, InputBalance, InputFile, MarkError, Modal, TxButton } from '@polkadot/react-components';
import { useAccountId, useApi, useNonEmptyString, useNonZeroBn, useStepper } from '@polkadot/react-hooks';
import { useAccountId, useApi, useFormField, useNonEmptyString, useStepper } from '@polkadot/react-hooks';
import { Available } from '@polkadot/react-query';
import { keyring } from '@polkadot/ui-keyring';
import { isNull, isWasm, stringify } from '@polkadot/util';
import { BN, BN_ZERO, isNull, isWasm, stringify } from '@polkadot/util';
import { ENDOWMENT } from '../constants';
import { ABI, InputMegaGas, InputName, MessageSignature, Params } from '../shared';
import store from '../store';
import { useTranslation } from '../translate';
@ -31,13 +30,15 @@ function Upload ({ onClose }: Props): React.ReactElement {
const [step, nextStep, prevStep] = useStepper();
const [[uploadTx, error], setUploadTx] = useState<[SubmittableExtrinsic<'promise'> | null, string | null]>([null, null]);
const [constructorIndex, setConstructorIndex] = useState<number>(0);
const [endowment, isEndowmentValid, setEndowment] = useNonZeroBn(ENDOWMENT);
const [value, isValueValid, setValue] = useFormField<BN>(BN_ZERO);
const [params, setParams] = useState<unknown[]>([]);
const [[wasm, isWasmValid], setWasm] = useState<[Uint8Array | null, boolean]>([null, false]);
const [name, isNameValid, setName] = useNonEmptyString();
const { abiName, contractAbi, errorText, isAbiError, isAbiSupplied, isAbiValid, onChangeAbi, onRemoveAbi } = useAbi();
const weight = useWeight();
const hasStorageDeposit = api.tx.contracts.instantiate.meta.args.length === 6;
const code = useMemo(
() => isAbiValid && isWasmValid && wasm && contractAbi
? new CodePromise(api, contractAbi, wasm)
@ -87,10 +88,11 @@ function Upload ({ onClose }: Props): React.ReactElement {
let error: string | null = null;
try {
contract = code && contractAbi?.constructors[constructorIndex]?.method && endowment
contract = code && contractAbi?.constructors[constructorIndex]?.method && value
? code.tx[contractAbi.constructors[constructorIndex].method]({
gasLimit: weight.weight,
value: endowment
storageDepositLimit: null,
value
}, ...params)
: null;
} catch (e) {
@ -98,7 +100,7 @@ function Upload ({ onClose }: Props): React.ReactElement {
}
setUploadTx(() => [contract, error]);
}, [code, contractAbi, constructorIndex, endowment, params, weight]);
}, [code, contractAbi, constructorIndex, value, params, weight]);
const _onAddWasm = useCallback(
(wasm: Uint8Array, name: string): void => {
@ -199,14 +201,15 @@ function Upload ({ onClose }: Props): React.ReactElement {
registry={contractAbi.registry}
/>
<InputBalance
help={t<string>('The allotted endowment for the deployed contract, i.e. the amount transferred to the contract upon instantiation.')}
isError={!isEndowmentValid}
label={t<string>('endowment')}
onChange={setEndowment}
value={endowment}
help={t<string>('The balance to transfer from the `origin` to the newly created contract.')}
isError={!isValueValid}
isZeroable={hasStorageDeposit}
label={t<string>('value')}
onChange={setValue}
value={value}
/>
<InputMegaGas
help={t<string>('The maximum amount of gas that can be used by this deployment, if the code requires more, the deployment will fail.')}
help={t<string>('The maximum amount of gas that can be used by this deployment, if the code requires more, the deployment will fail')}
weight={weight}
/>
{error && (

View File

@ -11,7 +11,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { Button, Dropdown, Expander, InputAddress, InputBalance, Modal, Toggle, TxButton } from '@polkadot/react-components';
import { useAccountId, useDebounce, useFormField, useToggle } from '@polkadot/react-hooks';
import { useAccountId, useApi, useDebounce, useFormField, useToggle } from '@polkadot/react-hooks';
import { Available } from '@polkadot/react-query';
import { BN_ONE, BN_ZERO } from '@polkadot/util';
@ -34,10 +34,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<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const message = contract.abi.messages[messageIndex];
const [accountId, setAccountId] = useAccountId();
const [estimatedWeight, setEstimatedWeight] = useState<BN | null>(null);
const [value, isValueValid, setEndowment] = useFormField<BN>(BN_ZERO);
const [value, isValueValid, setValue] = useFormField<BN>(BN_ZERO);
const [outcomes, setOutcomes] = useState<CallResult[]>([]);
const [execTx, setExecTx] = useState<SubmittableExtrinsic<'promise'> | null>(null);
const [params, setParams] = useState<unknown[]>([]);
@ -45,6 +46,7 @@ function Call ({ className = '', contract, messageIndex, onCallResult, onChangeM
const weight = useWeight();
const dbValue = useDebounce(value);
const dbParams = useDebounce(params);
const hasStorageDeposit = api.tx.contracts.call.meta.args.length === 5;
useEffect((): void => {
setEstimatedWeight(null);
@ -54,65 +56,53 @@ function Call ({ className = '', contract, messageIndex, onCallResult, onChangeM
useEffect((): void => {
value && message.isMutating && setExecTx((): SubmittableExtrinsic<'promise'> | null => {
try {
return contract.tx[message.method]({
gasLimit: weight.weight,
value: message.isPayable
? value
: 0
}, ...params);
return hasStorageDeposit
? contract.tx[message.method]({ gasLimit: weight.weight, storageDepositLimit: null, value: message.isPayable ? value : 0 }, ...params)
: contract.tx[message.method]({ gasLimit: weight.weight, value: message.isPayable ? value : 0 }, ...params);
} catch (error) {
return null;
}
});
}, [accountId, contract, message, value, weight, params]);
}, [accountId, contract, message, value, weight, params, hasStorageDeposit]);
useEffect((): void => {
if (!accountId || !message || !dbParams || !dbValue) return;
const query = hasStorageDeposit
? contract.query[message.method](accountId, { gasLimit: -1, storageDepositLimit: null, value: message.isPayable ? dbValue : 0 }, ...dbParams)
: contract.query[message.method](accountId, { gasLimit: -1, value: message.isPayable ? dbValue : 0 }, ...dbParams);
contract
.query[message.method](accountId, {
gasLimit: -1,
value: message.isPayable
? dbValue
: 0
}, ...dbParams)
query
.then(({ gasRequired, result }) => setEstimatedWeight(
result.isOk
? gasRequired
: null
))
.catch(() => setEstimatedWeight(null));
}, [accountId, contract, message, dbParams, dbValue]);
}, [accountId, contract, message, dbParams, dbValue, hasStorageDeposit]);
const _onSubmitRpc = useCallback(
(): void => {
if (!accountId || !message || !value || !weight) return;
const query = hasStorageDeposit
? contract.query[message.method](accountId, { gasLimit: weight.isEmpty ? -1 : weight.weight, storageDepositLimit: null, value: message.isPayable ? value : 0 }, ...params)
: contract.query[message.method](accountId, { gasLimit: weight.isEmpty ? -1 : weight.weight, value: message.isPayable ? value : 0 }, ...params);
contract
.query[message.method](accountId, {
gasLimit: weight.isEmpty
? -1
: weight.weight,
value: message.isPayable
? value
: 0
}, ...params)
.then((result): void => {
setOutcomes([{
...result,
from: accountId,
message,
params,
when: new Date()
}, ...outcomes]);
onCallResult && onCallResult(messageIndex, result);
})
query.then((result): void => {
setOutcomes([{
...result,
from: accountId,
message,
params,
when: new Date()
}, ...outcomes]);
onCallResult && onCallResult(messageIndex, result);
})
.catch((error): void => {
console.error(error);
onCallResult && onCallResult(messageIndex);
});
},
[accountId, contract, message, messageIndex, onCallResult, outcomes, params, value, weight]
[accountId, contract.query, hasStorageDeposit, message, messageIndex, onCallResult, outcomes, params, value, weight]
);
const _onClearOutcome = useCallback(
@ -180,7 +170,7 @@ function Call ({ className = '', contract, messageIndex, onCallResult, onChangeM
isError={!isValueValid}
isZeroable
label={t<string>('value')}
onChange={setEndowment}
onChange={setValue}
value={value}
/>
)}

View File

@ -8,13 +8,12 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { BlueprintPromise } from '@polkadot/api-contract';
import { Dropdown, Input, InputAddress, InputBalance, Modal, Toggle, TxButton } from '@polkadot/react-components';
import { useApi, useFormField, useNonEmptyString, useNonZeroBn } from '@polkadot/react-hooks';
import { useApi, useFormField, useNonEmptyString } from '@polkadot/react-hooks';
import { Available } from '@polkadot/react-query';
import { keyring } from '@polkadot/ui-keyring';
import { isHex, stringify } from '@polkadot/util';
import { BN, BN_ZERO, isHex, stringify } from '@polkadot/util';
import { randomAsHex } from '@polkadot/util-crypto';
import { ENDOWMENT } from '../constants';
import { ABI, InputMegaGas, InputName, MessageSignature, Params } from '../shared';
import store from '../store';
import { useTranslation } from '../translate';
@ -31,13 +30,15 @@ interface Props {
function Deploy ({ codeHash, constructorIndex = 0, onClose, setConstructorIndex }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const weight = useWeight();
const [initTx, setInitTx] = useState<SubmittableExtrinsic<'promise'> | null>(null);
const [params, setParams] = useState<unknown[]>([]);
const [accountId, isAccountIdValid, setAccountId] = useFormField<string | null>(null);
const [endowment, isEndowmentValid, setEndowment] = useNonZeroBn(ENDOWMENT);
const [value, isValueValid, setValue] = useFormField<BN>(BN_ZERO);
const [params, setParams] = useState<unknown[]>([]);
const [salt, setSalt] = useState<string>(() => randomAsHex());
const [withSalt, setWithSalt] = useState(false);
const weight = useWeight();
const hasStorageDeposit = api.tx.contracts.instantiate.meta.args.length === 6;
useEffect((): void => {
setParams([]);
@ -76,7 +77,7 @@ function Deploy ({ codeHash, constructorIndex = 0, onClose, setConstructorIndex
);
useEffect((): void => {
endowment && setInitTx((): SubmittableExtrinsic<'promise'> | null => {
value && setInitTx((): SubmittableExtrinsic<'promise'> | null => {
if (blueprint && contractAbi?.constructors[constructorIndex]?.method) {
try {
return blueprint.tx[contractAbi.constructors[constructorIndex].method]({
@ -84,7 +85,8 @@ function Deploy ({ codeHash, constructorIndex = 0, onClose, setConstructorIndex
salt: withSalt
? salt
: null,
value: endowment
storageDepositLimit: null,
value
}, ...params);
} catch (error) {
return null;
@ -93,7 +95,7 @@ function Deploy ({ codeHash, constructorIndex = 0, onClose, setConstructorIndex
return null;
});
}, [blueprint, contractAbi, constructorIndex, endowment, params, salt, weight, withSalt]);
}, [blueprint, contractAbi, constructorIndex, value, params, salt, weight, withSalt]);
const _onSuccess = useCallback(
(result: BlueprintSubmittableResult): void => {
@ -114,7 +116,7 @@ function Deploy ({ codeHash, constructorIndex = 0, onClose, setConstructorIndex
);
const isSaltValid = !withSalt || (salt && (!salt.startsWith('0x') || isHex(salt)));
const isValid = isNameValid && isEndowmentValid && weight.isValid && isAccountIdValid && isSaltValid;
const isValid = isNameValid && isValueValid && weight.isValid && isAccountIdValid && isSaltValid;
return (
<Modal
@ -171,11 +173,12 @@ function Deploy ({ codeHash, constructorIndex = 0, onClose, setConstructorIndex
</>
)}
<InputBalance
help={t<string>('The allotted endowment for this contract, i.e. the amount transferred to the contract upon instantiation.')}
isError={!isEndowmentValid}
label={t<string>('endowment')}
onChange={setEndowment}
value={endowment}
help={t<string>('The balance to transfer from the `origin` to the newly created contract.')}
isError={!isValueValid}
isZeroable={hasStorageDeposit}
label={t<string>('value')}
onChange={setValue}
value={value}
/>
<Input
help={t<string>('A hex or string value that acts as a salt for this deployment.')}