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:
parent
69498912d9
commit
707ae0b696
@ -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
|
||||
}));
|
||||
}
|
||||
|
||||
@ -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: ''
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
83
packages/app-contracts/src/ValidateAddr.tsx
Normal file
83
packages/app-contracts/src/ValidateAddr.tsx
Normal 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)
|
||||
);
|
||||
74
packages/app-contracts/src/ValidateCode.tsx
Normal file
74
packages/app-contracts/src/ValidateCode.tsx
Normal 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)
|
||||
);
|
||||
48
packages/ui-app/src/InfoForInput.tsx
Normal file
48
packages/ui-app/src/InfoForInput.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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>,
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user