Edit ETH value, gas and gas price in Contract Deployment (#4919)
* Fix typo * Add Value capabilities to Contract Deployment * Add Extras settings for Contract Deployment (#4483) * Fix deploy in API
This commit is contained in:
committed by
Gav Wood
parent
57d718fde1
commit
7846544c1b
@@ -16,12 +16,16 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { MenuItem } from 'material-ui';
|
||||
import { Checkbox, MenuItem } from 'material-ui';
|
||||
|
||||
import { AddressSelect, Form, Input, Select } from '~/ui';
|
||||
import { validateAbi } from '~/util/validation';
|
||||
import { parseAbiType } from '~/util/abi';
|
||||
|
||||
const CHECK_STYLE = {
|
||||
marginTop: '1em'
|
||||
};
|
||||
|
||||
export default class DetailsStep extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
@@ -30,8 +34,10 @@ export default class DetailsStep extends Component {
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
onAbiChange: PropTypes.func.isRequired,
|
||||
onAmountChange: PropTypes.func.isRequired,
|
||||
onCodeChange: PropTypes.func.isRequired,
|
||||
onDescriptionChange: PropTypes.func.isRequired,
|
||||
onExtrasChange: PropTypes.func.isRequired,
|
||||
onFromAddressChange: PropTypes.func.isRequired,
|
||||
onInputsChange: PropTypes.func.isRequired,
|
||||
onNameChange: PropTypes.func.isRequired,
|
||||
@@ -39,11 +45,14 @@ export default class DetailsStep extends Component {
|
||||
|
||||
abi: PropTypes.string,
|
||||
abiError: PropTypes.string,
|
||||
amount: PropTypes.string,
|
||||
amountError: PropTypes.string,
|
||||
balances: PropTypes.object,
|
||||
code: PropTypes.string,
|
||||
codeError: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
descriptionError: PropTypes.string,
|
||||
extras: PropTypes.bool,
|
||||
fromAddress: PropTypes.string,
|
||||
fromAddressError: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
@@ -52,6 +61,7 @@ export default class DetailsStep extends Component {
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
extras: false,
|
||||
readOnly: false
|
||||
};
|
||||
|
||||
@@ -83,7 +93,7 @@ export default class DetailsStep extends Component {
|
||||
fromAddress, fromAddressError,
|
||||
name, nameError,
|
||||
description, descriptionError,
|
||||
abiError,
|
||||
abiError, extras,
|
||||
code, codeError
|
||||
} = this.props;
|
||||
|
||||
@@ -189,10 +199,70 @@ export default class DetailsStep extends Component {
|
||||
value={ code }
|
||||
/>
|
||||
|
||||
{ this.renderValueInput() }
|
||||
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={ extras }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='deployContract.details.advanced.label'
|
||||
defaultMessage='advanced sending options'
|
||||
/>
|
||||
}
|
||||
onCheck={ this.onCheckExtras }
|
||||
style={ CHECK_STYLE }
|
||||
/>
|
||||
</div>
|
||||
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
renderValueInput () {
|
||||
const { abi, amount, amountError } = this.props;
|
||||
|
||||
let payable = false;
|
||||
|
||||
try {
|
||||
const parsedAbi = JSON.parse(abi);
|
||||
|
||||
payable = parsedAbi.find((method) => method.type === 'constructor' && method.payable);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!payable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
error={ amountError }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='deployContract.details.amount.hint'
|
||||
defaultMessage='the amount to transfer to the contract'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='deployContract.details.amount.label'
|
||||
defaultMessage='amount to transfer (in {tag})'
|
||||
values={ {
|
||||
tag: 'ETH'
|
||||
} }
|
||||
/>
|
||||
}
|
||||
min={ 0 }
|
||||
step={ 0.1 }
|
||||
type='number'
|
||||
onChange={ this.onAmountChange }
|
||||
value={ amount }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderContractSelect () {
|
||||
const { contracts } = this.state;
|
||||
|
||||
@@ -295,6 +365,16 @@ export default class DetailsStep extends Component {
|
||||
onDescriptionChange(description);
|
||||
}
|
||||
|
||||
onAmountChange = (event, value) => {
|
||||
const { onAmountChange } = this.props;
|
||||
|
||||
onAmountChange(value);
|
||||
}
|
||||
|
||||
onCheckExtras = () => {
|
||||
this.props.onExtrasChange(!this.props.extras);
|
||||
}
|
||||
|
||||
onAbiChange = (abi) => {
|
||||
const { api } = this.context;
|
||||
const { onAbiChange, onParamsChange, onInputsChange } = this.props;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { pick } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
@@ -22,12 +23,13 @@ import { connect } from 'react-redux';
|
||||
|
||||
import { BusyStep, Button, CompletedStep, CopyToClipboard, GasPriceEditor, IdentityIcon, Portal, TxHash, Warning } from '~/ui';
|
||||
import { CancelIcon, DoneIcon } from '~/ui/Icons';
|
||||
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
|
||||
import { ERRORS, validateAbi, validateCode, validateName, validatePositiveNumber } from '~/util/validation';
|
||||
import { deploy, deployEstimateGas } from '~/util/tx';
|
||||
|
||||
import DetailsStep from './DetailsStep';
|
||||
import ParametersStep from './ParametersStep';
|
||||
import ErrorStep from './ErrorStep';
|
||||
import Extras from '../Transfer/Extras';
|
||||
|
||||
import styles from './deployContract.css';
|
||||
|
||||
@@ -50,6 +52,14 @@ const STEPS = {
|
||||
/>
|
||||
)
|
||||
},
|
||||
EXTRAS: {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id='deployContract.title.extras'
|
||||
defaultMessage='extra information'
|
||||
/>
|
||||
)
|
||||
},
|
||||
DEPLOYMENT: {
|
||||
waiting: true,
|
||||
title: (
|
||||
@@ -97,12 +107,16 @@ class DeployContract extends Component {
|
||||
state = {
|
||||
abi: '',
|
||||
abiError: ERRORS.invalidAbi,
|
||||
amount: '0',
|
||||
amountValue: new BigNumber(0),
|
||||
amountError: '',
|
||||
code: '',
|
||||
codeError: ERRORS.invalidCode,
|
||||
deployState: '',
|
||||
deployError: null,
|
||||
description: '',
|
||||
descriptionError: null,
|
||||
extras: false,
|
||||
fromAddress: Object.keys(this.props.accounts)[0],
|
||||
fromAddressError: null,
|
||||
name: '',
|
||||
@@ -144,7 +158,19 @@ class DeployContract extends Component {
|
||||
|
||||
const realStepKeys = deployError || rejected
|
||||
? []
|
||||
: Object.keys(STEPS).filter((k) => k !== 'CONTRACT_PARAMETERS' || inputs.length > 0);
|
||||
: Object.keys(STEPS)
|
||||
.filter((k) => {
|
||||
if (k === 'CONTRACT_PARAMETERS') {
|
||||
return inputs.length > 0;
|
||||
}
|
||||
|
||||
if (k === 'EXTRAS') {
|
||||
return this.state.extras;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const realStep = realStepKeys.findIndex((k) => k === step);
|
||||
const realSteps = realStepKeys.length
|
||||
? realStepKeys.map((k) => STEPS[k])
|
||||
@@ -207,8 +233,8 @@ class DeployContract extends Component {
|
||||
}
|
||||
|
||||
renderDialogActions () {
|
||||
const { deployError, abiError, codeError, nameError, descriptionError, fromAddressError, fromAddress, step } = this.state;
|
||||
const isValid = !nameError && !fromAddressError && !descriptionError && !abiError && !codeError;
|
||||
const { deployError, abiError, amountError, codeError, nameError, descriptionError, fromAddressError, fromAddress, step } = this.state;
|
||||
const isValid = !nameError && !fromAddressError && !descriptionError && !abiError && !codeError && !amountError;
|
||||
|
||||
const cancelBtn = (
|
||||
<Button
|
||||
@@ -256,48 +282,69 @@ class DeployContract extends Component {
|
||||
return closeBtn;
|
||||
}
|
||||
|
||||
const createButton = (
|
||||
<Button
|
||||
icon={
|
||||
<IdentityIcon
|
||||
address={ fromAddress }
|
||||
button
|
||||
/>
|
||||
}
|
||||
key='create'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='deployContract.button.create'
|
||||
defaultMessage='Create'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onDeployStart }
|
||||
/>
|
||||
);
|
||||
|
||||
const nextButton = (
|
||||
<Button
|
||||
disabled={ !isValid }
|
||||
key='next'
|
||||
icon={
|
||||
<IdentityIcon
|
||||
address={ fromAddress }
|
||||
button
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='deployContract.button.next'
|
||||
defaultMessage='Next'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onNextStep }
|
||||
/>
|
||||
);
|
||||
|
||||
const hasParameters = this.state.inputs.length > 0;
|
||||
const showExtras = this.state.extras;
|
||||
|
||||
switch (step) {
|
||||
case 'CONTRACT_DETAILS':
|
||||
return [
|
||||
cancelBtn,
|
||||
<Button
|
||||
disabled={ !isValid }
|
||||
key='next'
|
||||
icon={
|
||||
<IdentityIcon
|
||||
address={ fromAddress }
|
||||
button
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='deployContract.button.next'
|
||||
defaultMessage='Next'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onParametersStep }
|
||||
/>
|
||||
hasParameters || showExtras
|
||||
? nextButton
|
||||
: createButton
|
||||
];
|
||||
|
||||
case 'CONTRACT_PARAMETERS':
|
||||
return [
|
||||
cancelBtn,
|
||||
<Button
|
||||
icon={
|
||||
<IdentityIcon
|
||||
address={ fromAddress }
|
||||
button
|
||||
/>
|
||||
}
|
||||
key='create'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='deployContract.button.create'
|
||||
defaultMessage='Create'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onDeployStart }
|
||||
/>
|
||||
showExtras
|
||||
? nextButton
|
||||
: createButton
|
||||
];
|
||||
|
||||
case 'EXTRAS':
|
||||
return [
|
||||
cancelBtn,
|
||||
createButton
|
||||
];
|
||||
|
||||
case 'DEPLOYMENT':
|
||||
@@ -344,6 +391,8 @@ class DeployContract extends Component {
|
||||
{ ...this.state }
|
||||
accounts={ accounts }
|
||||
balances={ balances }
|
||||
onAmountChange={ this.onAmountChange }
|
||||
onExtrasChange={ this.onExtrasChange }
|
||||
onFromAddressChange={ this.onFromAddressChange }
|
||||
onDescriptionChange={ this.onDescriptionChange }
|
||||
onNameChange={ this.onNameChange }
|
||||
@@ -365,6 +414,9 @@ class DeployContract extends Component {
|
||||
/>
|
||||
);
|
||||
|
||||
case 'EXTRAS':
|
||||
return this.renderExtrasPage();
|
||||
|
||||
case 'DEPLOYMENT':
|
||||
const body = txhash
|
||||
? <TxHash hash={ txhash } />
|
||||
@@ -411,17 +463,32 @@ class DeployContract extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
renderExtrasPage () {
|
||||
if (!this.gasStore.histogram) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Extras
|
||||
gasStore={ this.gasStore }
|
||||
hideData
|
||||
isEth
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
estimateGas = () => {
|
||||
const { api } = this.context;
|
||||
const { abiError, abiParsed, code, codeError, fromAddress, fromAddressError, params } = this.state;
|
||||
const { abiError, abiParsed, amountValue, amountError, code, codeError, fromAddress, fromAddressError, params } = this.state;
|
||||
|
||||
if (abiError || codeError || fromAddressError) {
|
||||
if (abiError || codeError || fromAddressError || amountError) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
data: code,
|
||||
from: fromAddress
|
||||
from: fromAddress,
|
||||
value: amountValue
|
||||
};
|
||||
|
||||
const contract = api.newContract(abiParsed);
|
||||
@@ -437,6 +504,19 @@ class DeployContract extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
onNextStep = () => {
|
||||
switch (this.state.step) {
|
||||
case 'CONTRACT_DETAILS':
|
||||
return this.onParametersStep();
|
||||
|
||||
case 'CONTRACT_PARAMETERS':
|
||||
return this.onExtrasStep();
|
||||
|
||||
default:
|
||||
console.warn('wrong call of "onNextStep" from', this.state.step);
|
||||
}
|
||||
}
|
||||
|
||||
onParametersStep = () => {
|
||||
const { inputs } = this.state;
|
||||
|
||||
@@ -444,6 +524,14 @@ class DeployContract extends Component {
|
||||
return this.setState({ step: 'CONTRACT_PARAMETERS' });
|
||||
}
|
||||
|
||||
return this.onExtrasStep();
|
||||
}
|
||||
|
||||
onExtrasStep = () => {
|
||||
if (this.state.extras) {
|
||||
return this.setState({ step: 'EXTRAS' });
|
||||
}
|
||||
|
||||
return this.onDeployStart();
|
||||
}
|
||||
|
||||
@@ -488,10 +576,24 @@ class DeployContract extends Component {
|
||||
this.setState(validateCode(code), this.estimateGas);
|
||||
}
|
||||
|
||||
onAmountChange = (amount) => {
|
||||
const { numberError } = validatePositiveNumber(amount);
|
||||
const nextAmountValue = numberError
|
||||
? new BigNumber(0)
|
||||
: this.context.api.util.toWei(amount);
|
||||
|
||||
this.gasStore.setEthValue(nextAmountValue);
|
||||
this.setState({ amount, amountValue: nextAmountValue, amountError: numberError }, this.estimateGas);
|
||||
}
|
||||
|
||||
onExtrasChange = (extras) => {
|
||||
this.setState({ extras });
|
||||
}
|
||||
|
||||
onDeployStart = () => {
|
||||
const { api, store } = this.context;
|
||||
const { source } = this.props;
|
||||
const { abiParsed, code, description, name, params, fromAddress } = this.state;
|
||||
const { abiParsed, amountValue, code, description, name, params, fromAddress } = this.state;
|
||||
|
||||
const metadata = {
|
||||
abi: abiParsed,
|
||||
@@ -503,16 +605,17 @@ class DeployContract extends Component {
|
||||
source
|
||||
};
|
||||
|
||||
const options = {
|
||||
const options = this.gasStore.overrideTransaction({
|
||||
data: code,
|
||||
from: fromAddress
|
||||
};
|
||||
from: fromAddress,
|
||||
value: amountValue
|
||||
});
|
||||
|
||||
this.setState({ step: 'DEPLOYMENT' });
|
||||
|
||||
const contract = api.newContract(abiParsed);
|
||||
|
||||
deploy(contract, options, params, metadata, this.onDeploymentState)
|
||||
deploy(contract, options, params, metadata, this.onDeploymentState, true)
|
||||
.then((address) => {
|
||||
// No contract address given, might need some confirmations
|
||||
// from the wallet owners...
|
||||
|
||||
Reference in New Issue
Block a user