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:
parent
8a0b974374
commit
c2be422be0
@ -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 && (
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -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.')}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user