ghost-portal/packages/app-contracts/src/Code.tsx
Jaco Greeff 26ff33678e
Store the genesisHash along with code & contracts (load check) (#982)
* Store the genesisHash along with code & contracts (load check)

* Cleanup hanging promises

* contract:contract: -> contract:address: (storage)

* Update store.ts
2019-04-11 21:19:14 +02:00

263 lines
7.0 KiB
TypeScript

// Copyright 2017-2019 @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 { SubmittableResult } from '@polkadot/api/SubmittableExtrinsic';
import { I18nProps } from '@polkadot/ui-app/types';
import { ComponentProps } from './types';
import BN from 'bn.js';
import React from 'react';
import { Button, Input, InputAddress, InputFile, InputNumber, TxButton } from '@polkadot/ui-app';
import { compactAddLength, isHex } from '@polkadot/util';
import { Hash } from '@polkadot/types';
import ABI from './ABI';
import store from './store';
import translate from './translate';
type Props = ComponentProps & I18nProps;
type State = {
abi?: string | null,
accountId?: string | null,
codeHash?: string | null,
gasLimit: BN,
isAbiValid: boolean,
isBusy: boolean,
isHashValid: boolean,
isNameValid: boolean,
isNew: boolean,
isWasmValid: boolean,
name?: string | null,
wasm?: Uint8Array | null
};
class Deploy extends React.PureComponent<Props, State> {
state: State = {
accountId: null,
gasLimit: new BN(0),
isAbiValid: true,
isBusy: false,
isHashValid: false,
isNew: true,
isNameValid: false,
isWasmValid: false
};
render () {
const { t } = this.props;
const { isNew } = this.state;
return (
<div className='contracts--Code'>
<Button.Group isBasic isCentered>
<Button
isBasic
isNegative={isNew}
label={t('deploy new')}
onClick={this.toggleNew}
/>
<Button
isBasic
isNegative={!isNew}
label={t('attach existing')}
onClick={this.toggleNew}
/>
</Button.Group>
{
isNew
? this.renderDeploy()
: this.renderExisting()
}
</div>
);
}
private renderDeploy () {
const { t } = this.props;
const { accountId, gasLimit, isAbiValid, isBusy, isNameValid, isWasmValid, wasm } = this.state;
const isValid = !isBusy && isAbiValid && isNameValid && isWasmValid && !gasLimit.isZero() && !!accountId;
return (
<>
<InputAddress
help={t('Specify the user account to use for this deployment. And fees will be deducted from this account.')}
label={t('deployment account')}
onChange={this.onChangeAccount}
type='account'
/>
<InputFile
help={t('The compiled WASM for the contract that you wish to deploy. Ecah unique code blob will be attached with a code hash that can be used to create new instances.')}
isError={!isWasmValid}
label={t('compiled contract WASM')}
onChange={this.onAddWasm}
/>
{this.renderInputName()}
{this.renderInputAbi()}
<InputNumber
help={t('The maximum amount of gas that can be used by this deployment, if the code requires more, the deployment will fail.')}
label={t('maximum gas allowed')}
onChange={this.onChangeGas}
/>
<Button.Group>
<TxButton
accountId={accountId}
isDisabled={!isValid}
isPrimary
label={t('Deploy')}
onClick={this.toggleBusy}
onFailed={this.toggleBusy}
onSuccess={this.onSuccess}
params={[gasLimit, wasm]}
tx='contract.putCode'
/>
</Button.Group>
</>
);
}
private renderExisting () {
const { t } = this.props;
const { codeHash, isAbiValid, isHashValid, isNameValid } = this.state;
const isValid = isAbiValid && isHashValid && isNameValid;
return (
<>
<Input
autoFocus
help={t('The code hash for the on-chain deployed code.')}
isError={!isHashValid}
label={t('code hash')}
onChange={this.onChangeHash}
value={codeHash}
/>
{this.renderInputName()}
{this.renderInputAbi()}
<Button.Group>
<Button
isDisabled={!isValid}
isPrimary
label={t('Save')}
onClick={this.onSave}
/>
</Button.Group>
</>
);
}
private renderInputAbi () {
const { t } = this.props;
const { isAbiValid } = this.state;
return (
<ABI
help={t('The ABI for the WASM code. In this step it is optional, but setting it here simplifies the setup of contract instances.')}
isError={!isAbiValid}
label={t('contract ABI (optional)')}
onChange={this.onAddAbi}
/>
);
}
private renderInputName () {
const { t } = this.props;
const { isNameValid, name } = this.state;
return (
<Input
help={t('A name for this WASM code that helps to user distinguish. Only used for display purposes.')}
isError={!isNameValid}
label={t('code bundle name')}
onChange={this.onChangeName}
value={name}
/>
);
}
private onAddAbi = (abi: string | null): void => {
this.setState({ abi, isAbiValid: !!abi });
}
private onAddWasm = (wasm: Uint8Array, name: string): void => {
this.setState({ wasm: compactAddLength(wasm), isWasmValid: true });
this.onChangeName(name);
}
private onChangeAccount = (accountId: string | null): void => {
this.setState({ accountId });
}
private onChangeGas = (gasLimit: BN | undefined): void => {
this.setState({ gasLimit: gasLimit || new BN(0) });
}
private onChangeHash = (codeHash: string): void => {
this.setState({ codeHash, isHashValid: isHex(codeHash) && codeHash.length === 66 });
}
private onChangeName = (name: string): void => {
this.setState({ name, isNameValid: name.length !== 0 });
}
private toggleBusy = (): void => {
this.setState(({ isBusy }) => ({
isBusy: !isBusy
}));
}
private toggleNew = (): void => {
this.setState(({ isNew }) => ({
abi: null,
codeHash: null,
isAbiValid: true,
isHashValid: false,
isNameValid: false,
name: null,
isNew: !isNew
}));
}
private onSave = (): void => {
const { abi, codeHash, name } = this.state;
if (!codeHash || !name) {
return;
}
store.saveCode(new Hash(codeHash), { abi, name }).catch((error) => {
console.error('Unable to save code', error);
});
this.redirect();
}
private onSuccess = (result: SubmittableResult): void => {
const record = result.findRecord('contract', 'CodeStored');
if (record) {
const codeHash = record.event.data[0];
this.setState(({ abi, name }) => {
if (!codeHash || !name) {
return;
}
store.saveCode(codeHash as Hash, { abi, name }).catch((error) => {
console.error('Unable to save code', error);
});
this.redirect();
});
}
this.toggleBusy();
}
private redirect () {
window.location.hash = this.props.basePath;
}
}
export default translate(Deploy);