Validate codehash (with wasm), address (with code) & wasm header (#991)

* Validate codehash (with wasm), address (with code) & wasm header

* Typo
This commit is contained in:
Jaco Greeff 2019-04-12 12:50:58 +02:00 committed by GitHub
parent 69498912d9
commit 707ae0b696
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 254 additions and 26 deletions

View File

@ -9,10 +9,11 @@ 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 { compactAddLength } from '@polkadot/util';
import { Hash } from '@polkadot/types';
import ABI from './ABI';
import ValidateCode from './ValidateCode';
import store from './store';
import translate from './translate';
@ -25,7 +26,7 @@ type State = {
gasLimit: BN,
isAbiValid: boolean,
isBusy: boolean,
isHashValid: boolean,
isCodeValid: boolean,
isNameValid: boolean,
isNew: boolean,
isWasmValid: boolean,
@ -39,7 +40,7 @@ class Deploy extends React.PureComponent<Props, State> {
gasLimit: new BN(0),
isAbiValid: true,
isBusy: false,
isHashValid: false,
isCodeValid: false,
isNew: true,
isNameValid: false,
isWasmValid: false
@ -88,10 +89,15 @@ class Deploy extends React.PureComponent<Props, State> {
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.')}
help={t('The compiled WASM for the contract that you wish to deploy. Each 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}
placeholder={
wasm && !isWasmValid
? t('The code is not recognized as being in valid WASM format')
: null
}
/>
{this.renderInputName()}
{this.renderInputAbi()}
@ -119,19 +125,23 @@ class Deploy extends React.PureComponent<Props, State> {
private renderExisting () {
const { t } = this.props;
const { codeHash, isAbiValid, isHashValid, isNameValid } = this.state;
const isValid = isAbiValid && isHashValid && isNameValid;
const { codeHash, isAbiValid, isCodeValid, isNameValid } = this.state;
const isValid = isAbiValid && isCodeValid && isNameValid;
return (
<>
<Input
autoFocus
help={t('The code hash for the on-chain deployed code.')}
isError={!isHashValid}
isError={!isCodeValid}
label={t('code hash')}
onChange={this.onChangeHash}
value={codeHash}
/>
<ValidateCode
codeHash={codeHash}
onChange={this.onValidateCode}
/>
{this.renderInputName()}
{this.renderInputAbi()}
<Button.Group>
@ -180,7 +190,9 @@ class Deploy extends React.PureComponent<Props, State> {
}
private onAddWasm = (wasm: Uint8Array, name: string): void => {
this.setState({ wasm: compactAddLength(wasm), isWasmValid: true });
const isWasmValid = wasm.subarray(0, 4).toString() === '0,97,115,109'; // '\0asm'
this.setState({ wasm: compactAddLength(wasm), isWasmValid });
this.onChangeName(name);
}
@ -193,13 +205,17 @@ class Deploy extends React.PureComponent<Props, State> {
}
private onChangeHash = (codeHash: string): void => {
this.setState({ codeHash, isHashValid: isHex(codeHash) && codeHash.length === 66 });
this.setState({ codeHash, isCodeValid: false });
}
private onChangeName = (name: string): void => {
this.setState({ name, isNameValid: name.length !== 0 });
}
private onValidateCode = (isCodeValid: boolean): void => {
this.setState({ isCodeValid });
}
private toggleBusy = (): void => {
this.setState(({ isBusy }) => ({
isBusy: !isBusy
@ -211,9 +227,9 @@ class Deploy extends React.PureComponent<Props, State> {
abi: null,
codeHash: null,
isAbiValid: true,
isHashValid: false,
isCodeValid: false,
isNameValid: false,
name: null,
name: '',
isNew: !isNew
}));
}

View File

@ -13,9 +13,9 @@ import { AccountId, ContractAbi } from '@polkadot/types';
import ABI from './ABI';
import Params from './Params';
import ValidateAddr from './ValidateAddr';
import store from './store';
import translate from './translate';
import keyring from '@polkadot/ui-keyring';
type Props = ComponentProps & I18nProps;
@ -197,6 +197,10 @@ class Create extends React.PureComponent<Props, State> {
onChange={this.onChangeAddress}
value={address}
/>
<ValidateAddr
address={address}
onChange={this.onValidateAddr}
/>
{this.renderInputName()}
{this.renderInputAbi()}
<Button.Group>
@ -259,17 +263,7 @@ class Create extends React.PureComponent<Props, State> {
}
private onChangeAddress = (address: string): void => {
let isAddressValid = false;
try {
keyring.decodeAddress(address);
isAddressValid = true;
} catch (error) {
// ignore
}
this.setState({ address, isAddressValid });
this.setState({ address, isAddressValid: false });
}
private onChangeCode = (codeHash: string): void => {
@ -304,6 +298,10 @@ class Create extends React.PureComponent<Props, State> {
this.setState({ params });
}
private onValidateAddr = (isAddressValid: boolean): void => {
this.setState({ isAddressValid });
}
private toggleBusy = (): void => {
this.setState(({ isBusy }) => ({
isBusy: !isBusy
@ -318,7 +316,7 @@ class Create extends React.PureComponent<Props, State> {
isAbiValid: false,
isNameValid: false,
isNew: !isNew,
name: null
name: ''
}));
}

View File

@ -0,0 +1,83 @@
// 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 { I18nProps } from '@polkadot/ui-app/types';
import { ApiProps } from '@polkadot/ui-api/types';
import React from 'react';
import { CodeHash, Option } from '@polkadot/types';
import { withCalls } from '@polkadot/ui-api';
import { InfoForInput } from '@polkadot/ui-app';
import keyring from '@polkadot/ui-keyring';
import translate from './translate';
type Props = ApiProps & I18nProps & {
address?: string | null,
contract_codeHashOf?: Option<CodeHash>,
onChange: (isValid: boolean) => void
};
type State = {
isStored: boolean,
isValidAddr: boolean,
isValid: boolean
};
class ValidateAddr extends React.PureComponent<Props> {
state: State = {
isStored: false,
isValidAddr: false,
isValid: false
};
static getDerivedStateFromProps ({ address, contract_codeHashOf, onChange }: Props): State {
let isValidAddr = false;
try {
keyring.decodeAddress(address || '');
isValidAddr = true;
} catch (error) {
// ignore
}
const isStored = !!contract_codeHashOf && contract_codeHashOf.isSome;
const isValid = isValidAddr && isStored;
// FIXME Really not convinced this is the correct place to do this type of callback?
onChange(isValid);
return {
isStored,
isValidAddr,
isValid
};
}
render () {
const { t } = this.props;
const { isValid, isValidAddr } = this.state;
if (isValid || !isValidAddr) {
return null;
}
return (
<InfoForInput type='error'>
{
isValidAddr
? t('Unable to find deployed contract code at the specified address')
: t('The value is not in a valid address format')
}
</InfoForInput>
);
}
}
export default translate(
withCalls<Props>(
['query.contract.codeHashOf', { paramName: 'address' }]
)(ValidateAddr)
);

View File

@ -0,0 +1,74 @@
// 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 { I18nProps } from '@polkadot/ui-app/types';
import { ApiProps } from '@polkadot/ui-api/types';
import React from 'react';
import { Option, PrefabWasmModule } from '@polkadot/types';
import { withCalls } from '@polkadot/ui-api';
import { InfoForInput } from '@polkadot/ui-app';
import { isHex } from '@polkadot/util';
import translate from './translate';
type Props = ApiProps & I18nProps & {
codeHash?: string | null,
contract_codeStorage?: Option<PrefabWasmModule>,
onChange: (isValid: boolean) => void
};
type State = {
isStored: boolean,
isValidHex: boolean,
isValid: boolean
};
class ValidateCode extends React.PureComponent<Props> {
state: State = {
isStored: false,
isValidHex: false,
isValid: false
};
static getDerivedStateFromProps ({ codeHash, contract_codeStorage, onChange }: Props): State {
const isValidHex = !!codeHash && isHex(codeHash) && codeHash.length === 66;
const isStored = !!contract_codeStorage && contract_codeStorage.isSome;
const isValid = isValidHex && isStored;
// FIXME Really not convinced this is the correct place to do this type of callback?
onChange(isValid);
return {
isStored,
isValidHex,
isValid
};
}
render () {
const { t } = this.props;
const { isValid, isValidHex } = this.state;
if (isValid || !isValidHex) {
return null;
}
return (
<InfoForInput type='error'>
{
isValidHex
? t('Unable to find on-chain WASM code for the supplied codeHash')
: t('The codeHash is not a valid hex hash')
}
</InfoForInput>
);
}
}
export default translate(
withCalls<Props>(
['query.contract.codeStorage', { paramName: 'codeHash' }]
)(ValidateCode)
);

View File

@ -0,0 +1,48 @@
// Copyright 2017-2019 @polkadot/ui-app 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 React from 'react';
import styled from 'styled-components';
import { classes } from './util';
import Labelled from './Labelled';
type Props = {
children: React.ReactNode
className?: string,
type?: 'error' | 'info' | 'warning';
};
const Wrapper = styled.div`
background: white;
border-radius: 0 0 0.25rem 0.25rem;
margin: -0.5rem 0 0.25rem;
padding: 1.25rem 1.5rem 1rem;
&.error {
background: #db2828;
color: #eee;
}
&.warning {
background: #ffffe0;
}
> ul {
margin: 0;
padding: 0;
}
`;
export default class InfoForInput extends React.PureComponent<Props> {
render () {
const { children, className, type = 'info' } = this.props;
return (
<Labelled>
<Wrapper className={classes(className, type)}>{children}</Wrapper>
</Labelled>
);
}
}

View File

@ -37,7 +37,7 @@ export type QueueTx = AccountInfo & {
rpc: RpcMethod,
signerCb?: SignerCallback,
signerOptions?: SignatureOptions,
txFailedCb?: TxCallback,
txFailedCb?: (status: SubmittableResult | null) => void,
txSuccessCb?: TxCallback,
txUpdateCb?: TxCallback,
values?: Array<any>,

View File

@ -18,6 +18,7 @@ export { default as Event } from './Event';
export { default as HelpOverlay } from './HelpOverlay';
export { default as Icon } from './Icon';
export { default as IdentityIcon } from './IdentityIcon';
export { default as InfoForInput } from './InfoForInput';
export { default as Input } from './Input';
export { default as InputAddress } from './InputAddress';
export { default as InputBalance } from './InputBalance';

View File

@ -229,13 +229,17 @@ class Signer extends React.PureComponent<Props, State> {
return;
}
const { id, signerCb } = currentItem;
const { id, signerCb, txFailedCb } = currentItem;
queueSetTxStatus(id, 'cancelled');
if (isFunction(signerCb)) {
signerCb(id, false);
}
if (isFunction(txFailedCb)) {
txFailedCb(null);
}
}
private onSend = async (): Promise<void> => {
@ -352,6 +356,10 @@ class Signer extends React.PureComponent<Props, State> {
} catch (error) {
console.error('makeExtrinsicCall: error:', error.message);
queueSetTxStatus(id, 'error', {}, error);
if (isFunction(txFailedCb)) {
txFailedCb(null);
}
}
}