ghost-portal/packages/page-contracts/src/Deploy.tsx
Keith Ingram 2081464de8
Rewrite & update to page-contracts (#2873)
* first commit

* finish

* InputMegaGas, use BN consts'

* update util'

* tags issue

* manifest

* import bn_zero

* rm

* restore contract tags

* fix tags onClose

* sec to ms

* ms -> sec

Co-authored-by: Jaco Greeff <jacogr@gmail.com>
2020-06-09 14:56:10 +02:00

260 lines
8.3 KiB
TypeScript

// Copyright 2017-2020 @polkadot/app-contracts authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountId } from '@polkadot/types/interfaces';
import { StringOrNull, VoidFn } from '@polkadot/react-components/types';
import { CodeStored } from './types';
import BN from 'bn.js';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { SubmittableResult } from '@polkadot/api';
import { Dropdown, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
import { useFormField, useNonEmptyString, useNonZeroBn, useApi } from '@polkadot/react-hooks';
import keyring from '@polkadot/ui-keyring';
import { ABI, InputMegaGas, InputName, MessageSignature, Params } from './shared';
import store from './store';
import { useTranslation } from './translate';
import useAbi from './useAbi';
import useWeight from './useWeight';
import { ENDOWMENT } from './constants';
type CodeOptions = { text: string; value: string }[];
type ConstructOptions = { key: string; text: React.ReactNode; value: string }[];
interface Props {
basePath: string;
allCodes: CodeStored[];
codeHash: string;
constructorIndex?: number;
isOpen?: boolean;
onClose: VoidFn;
setCodeHash: React.Dispatch<string>;
setConstructorIndex: React.Dispatch<number>;
}
function defaultContractName (name: string) {
return `${name} (instance)`;
}
function Deploy ({ allCodes, basePath, codeHash, constructorIndex = 0, isOpen, onClose, setCodeHash, setConstructorIndex }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const history = useHistory();
const useWeightHook = useWeight();
const { isValid: isWeightValid, weight } = useWeightHook;
const code = useMemo(
(): CodeStored => store.getCode(codeHash),
[codeHash]
);
const [accountId, isAccountIdValid, setAccountId] = useFormField<StringOrNull>(null);
const [endowment, isEndowmentValid, setEndowment] = useNonZeroBn(new BN(ENDOWMENT));
const [name, isNameValid, setName] = useNonEmptyString(t(defaultContractName(code.json.name)));
const { abi, contractAbi, errorText, isAbiError, isAbiSupplied, isAbiValid, onChangeAbi, onRemoveAbi } = useAbi([code.json.abi || null, code.contractAbi || null], codeHash, true);
const isValid = useMemo(
(): boolean => isNameValid && isEndowmentValid && isWeightValid && isAccountIdValid,
[isAccountIdValid, isEndowmentValid, isNameValid, isWeightValid]
);
const codeOptions = useMemo(
(): CodeOptions => allCodes.map(({ json: { codeHash, name } }): { text: string; value: string } => ({
text: `${name} (${codeHash})`,
value: codeHash
})),
[allCodes]
);
const constructOptions = useMemo(
(): ConstructOptions => {
if (!contractAbi) {
return [];
}
return contractAbi.abi.contract.constructors.map(
(constr, index) => {
return {
key: `${index}`,
text: (
<MessageSignature
asConstructor
message={constr}
/>
),
value: `${index}`
};
});
},
[contractAbi]
);
const [params, setParams] = useState<any[]>(
contractAbi && constructorIndex >= 0 ? contractAbi.abi.contract.constructors[constructorIndex].args : []
);
useEffect(
(): void => {
setParams(contractAbi ? contractAbi.abi.contract.constructors[constructorIndex].args : []);
},
[constructorIndex, contractAbi]
);
useEffect(
(): void => {
setName(t(defaultContractName(code.json.name)));
},
[code, setName, t]
);
const _constructCall = useCallback(
(): any[] => {
if (!contractAbi || constructorIndex < 0) {
return [];
}
return [endowment, weight, codeHash, contractAbi.constructors[constructorIndex](...params)];
},
[codeHash, constructorIndex, contractAbi, endowment, params, weight]
);
const _onSuccess = useCallback(
(result: SubmittableResult): void => {
const section = api.tx.contracts ? 'contracts' : 'contract';
const records = result.filterRecords(section, 'Instantiated');
if (records.length) {
// find the last EventRecord (in the case of multiple contracts deployed - we should really be
// more clever here to find the exact contract deployed, this works for eg. Delegator)
const address = records[records.length - 1].event.data[1] as unknown as AccountId;
keyring.saveContract(address.toString(), {
contract: {
abi,
genesisHash: api.genesisHash.toHex()
},
name,
tags: []
});
history.push(basePath);
onClose && onClose();
}
},
[abi, api, basePath, history, name, onClose]
);
return (
<Modal
header={t('Add an existing code hash')}
onClose={onClose}
open={isOpen}
>
<Modal.Content>
<InputAddress
help={t('Specify the user account to use for this deployment. Any fees will be deducted from this account.')}
isInput={false}
label={t('deployment account')}
onChange={setAccountId}
type='account'
value={accountId}
/>
<Dropdown
defaultValue={
codeOptions.length
? codeOptions[codeOptions.length - 1].value
: undefined
}
help={t('The contract WASM previously deployed. Internally this is identified by the hash of the code, as either created or attached.')}
label={t('code for this contract')}
onChange={setCodeHash}
options={codeOptions}
value={codeHash}
/>
<InputName
isContract
isError={!isNameValid}
onChange={setName}
value={name || ''}
/>
{
isAbiSupplied
? null
: (
<ABI
contractAbi={contractAbi}
errorText={errorText}
isError={isAbiError}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
)
}
{
contractAbi
? (
<Dropdown
help={t<string>('The deployment constructor information for this contract, as provided by the ABI.')}
isDisabled={contractAbi.abi.contract.constructors.length <= 1}
label={t('deployment constructor')}
onChange={setConstructorIndex}
options={constructOptions}
value={`${constructorIndex}`}
/>
)
: null
}
<Params
onChange={setParams}
params={
contractAbi && constructorIndex >= 0
? contractAbi.abi.contract.constructors[constructorIndex].args
: []
}
/>
<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}
/>
<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.')}
label={t<string>('maximum gas allowed')}
{...useWeightHook}
/>
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<TxButton
accountId={accountId}
icon='cloud upload'
isDisabled={!isValid}
isPrimary
label={t('Deploy')}
onClick={onClose}
onSuccess={_onSuccess}
params={_constructCall}
tx={
api.tx.contracts
? (
!api.tx.contracts.instantiate
? 'contracts.create' // V2 (new)
: 'contracts.instantiate' // V2 (old)
)
: 'contract.create' // V1
}
withSpinner
/>
</Modal.Actions>
</Modal>
);
}
export default React.memo(Deploy);