Add block & timestamp conditions to Signer (#4411)

* WIP

* WIP (with lint)

* Update ui/RadioButtons

* transaction.condition

* Date & Time selection in-place

* Swap to condition-only

* Fix tests, align naming

* Pick error properly from validation

* Update tests

* condition: time sent withough ms

* Format numbers as base-10

* override popup styles (zIndex)

* Pass condition to signer

* Update expectation (failing test typo)

* Adjust min/max height for expanded bar

* Fix address display

* Fix name display

* Number inputs for gas/gasPrice/blockNumber

* Default blockNumber to 1 (align with min setting)

* Update tests with min value

* Add Block Number

* Fix failing tests (after blockNumber intro)
This commit is contained in:
Jaco Greeff
2017-02-03 20:01:09 +01:00
committed by GitHub
parent 312aa72747
commit cd4d489b57
31 changed files with 894 additions and 255 deletions

View File

@@ -15,45 +15,22 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { Input, GasPriceEditor } from '~/ui';
import { GasPriceEditor } from '~/ui';
import styles from '../executeContract.css';
export default class AdvancedStep extends Component {
static propTypes = {
gasStore: PropTypes.object.isRequired,
minBlock: PropTypes.string,
minBlockError: PropTypes.string,
onMinBlockChange: PropTypes.func
gasStore: PropTypes.object.isRequired
};
render () {
const { gasStore, minBlock, minBlockError, onMinBlockChange } = this.props;
const { gasStore } = this.props;
return (
<div>
<Input
error={ minBlockError }
hint={
<FormattedMessage
id='executeContract.advanced.minBlock.hint'
defaultMessage='Only post the transaction after this block'
/>
}
label={
<FormattedMessage
id='executeContract.advanced.minBlock.label'
defaultMessage='BlockNumber to send from'
/>
}
value={ minBlock }
onSubmit={ onMinBlockChange }
/>
<div className={ styles.gaseditor }>
<GasPriceEditor store={ gasStore } />
</div>
<div className={ styles.gaseditor }>
<GasPriceEditor store={ gasStore } />
</div>
);
}

View File

@@ -14,7 +14,6 @@
// 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';
@@ -100,8 +99,6 @@ class ExecuteContract extends Component {
fromAddressError: null,
func: null,
funcError: null,
minBlock: '0',
minBlockError: null,
rejected: false,
sending: false,
step: STEP_DETAILS,
@@ -167,8 +164,8 @@ class ExecuteContract extends Component {
renderDialogActions () {
const { onClose, fromAddress } = this.props;
const { advancedOptions, sending, step, fromAddressError, minBlockError, valuesError } = this.state;
const hasError = fromAddressError || minBlockError || valuesError.find((error) => error);
const { advancedOptions, sending, step, fromAddressError, valuesError } = this.state;
const hasError = fromAddressError || valuesError.find((error) => error);
const cancelBtn = (
<Button
@@ -258,7 +255,7 @@ class ExecuteContract extends Component {
renderStep () {
const { onFromAddressChange } = this.props;
const { advancedOptions, step, busyState, minBlock, minBlockError, txhash, rejected } = this.state;
const { advancedOptions, step, busyState, txhash, rejected } = this.state;
if (rejected) {
return (
@@ -305,12 +302,7 @@ class ExecuteContract extends Component {
);
} else if (advancedOptions && (step === STEP_BUSY_OR_ADVANCED)) {
return (
<AdvancedStep
gasStore={ this.gasStore }
minBlock={ minBlock }
minBlockError={ minBlockError }
onMinBlockChange={ this.onMinBlockChange }
/>
<AdvancedStep gasStore={ this.gasStore } />
);
}
@@ -339,15 +331,6 @@ class ExecuteContract extends Component {
}, this.estimateGas);
}
onMinBlockChange = (minBlock) => {
const minBlockError = validateUint(minBlock).valueError;
this.setState({
minBlock,
minBlockError
});
}
onValueChange = (event, index, _value) => {
const { func, values, valuesError } = this.state;
const input = func.inputs.find((input, _index) => index === _index);
@@ -409,17 +392,14 @@ class ExecuteContract extends Component {
postTransaction = () => {
const { api, store } = this.context;
const { fromAddress } = this.props;
const { advancedOptions, amount, func, minBlock, values } = this.state;
const { advancedOptions, amount, func, values } = this.state;
const steps = advancedOptions ? STAGES_ADVANCED : STAGES_BASIC;
const finalstep = steps.length - 1;
const options = {
gas: this.gasStore.gas,
gasPrice: this.gasStore.price,
const options = this.gasStore.overrideTransaction({
from: fromAddress,
minBlock: new BigNumber(minBlock || 0).gt(0) ? minBlock : null,
value: api.util.toWei(amount || 0)
};
});
this.setState({ sending: true, step: advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED });

View File

@@ -27,36 +27,17 @@ export default class Extras extends Component {
dataError: PropTypes.string,
gasStore: PropTypes.object.isRequired,
isEth: PropTypes.bool,
minBlock: PropTypes.string,
minBlockError: PropTypes.string,
onChange: PropTypes.func.isRequired,
total: PropTypes.string,
totalError: PropTypes.string
}
render () {
const { gasStore, minBlock, minBlockError, onChange } = this.props;
const { gasStore, onChange } = this.props;
return (
<Form>
{ this.renderData() }
<Input
error={ minBlockError }
hint={
<FormattedMessage
id='transferModal.minBlock.hint'
defaultMessage='Only post the transaction after this block'
/>
}
label={
<FormattedMessage
id='transferModal.minBlock.label'
defaultMessage='BlockNumber to send from'
/>
}
value={ minBlock }
onChange={ this.onEditMinBlock }
/>
<div className={ styles.gaseditor }>
<GasPriceEditor
store={ gasStore }
@@ -98,8 +79,4 @@ export default class Extras extends Component {
onEditData = (event) => {
this.props.onChange('data', event.target.value);
}
onEditMinBlock = (event) => {
this.props.onChange('minBlock', event.target.value);
}
}

View File

@@ -52,9 +52,6 @@ export default class TransferStore {
@observable data = '';
@observable dataError = null;
@observable minBlock = '0';
@observable minBlockError = null;
@observable recipient = '';
@observable recipientError = ERRORS.requireRecipient;
@@ -78,39 +75,6 @@ export default class TransferStore {
gasStore = null;
@computed get steps () {
const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC);
if (this.rejected) {
steps[steps.length - 1] = TITLES.rejected;
}
return steps;
}
@computed get isValid () {
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.minBlockError && !this.totalError;
const verifyValid = !this.passwordError;
switch (this.stage) {
case 0:
return detailsValid;
case 1:
return this.extras
? extrasValid
: verifyValid;
case 2:
return verifyValid;
}
}
get token () {
return this.balance.tokens.find((balance) => balance.token.tag === this.tag).token;
}
constructor (api, props) {
this.api = api;
@@ -135,6 +99,39 @@ export default class TransferStore {
}
}
@computed get steps () {
const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC);
if (this.rejected) {
steps[steps.length - 1] = TITLES.rejected;
}
return steps;
}
@computed get isValid () {
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.gasStore.conditionBlockError && !this.totalError;
const verifyValid = !this.passwordError;
switch (this.stage) {
case 0:
return detailsValid;
case 1:
return this.extras
? extrasValid
: verifyValid;
case 2:
return verifyValid;
}
}
get token () {
return this.balance.tokens.find((balance) => balance.token.tag === this.tag).token;
}
@action onNext = () => {
this.stage += 1;
}
@@ -164,9 +161,6 @@ export default class TransferStore {
case 'gasPrice':
return this._onUpdateGasPrice(value);
case 'minBlock':
return this._onUpdateMinBlock(value);
case 'recipient':
return this._onUpdateRecipient(value);
@@ -284,14 +278,6 @@ export default class TransferStore {
this.recalculate();
}
@action _onUpdateMinBlock = (minBlock) => {
console.log('minBlock', minBlock);
transaction(() => {
this.minBlock = minBlock;
this.minBlockError = this._validatePositiveNumber(minBlock);
});
}
@action _onUpdateGasPrice = (gasPrice) => {
this.recalculate();
}
@@ -590,7 +576,6 @@ export default class TransferStore {
send () {
const { options, values } = this._getTransferParams();
options.minBlock = new BigNumber(this.minBlock || 0).gt(0) ? this.minBlock : null;
log.debug('@send', 'transfer value', options.value && options.value.toFormat());
return this._getTransferMethod().postTransaction(options, values);
@@ -639,15 +624,12 @@ export default class TransferStore {
const to = (isEth && !isWallet) ? this.recipient
: (this.isWallet ? this.wallet.address : this.token.address);
const options = {
const options = this.gasStore.overrideTransaction({
from: this.sender || this.account.address,
to
};
});
if (!gas) {
options.gas = this.gasStore.gas;
options.gasPrice = this.gasStore.price;
} else {
if (gas) {
options.gas = MAX_GAS_ESTIMATION;
}

View File

@@ -207,7 +207,7 @@ class Transfer extends Component {
return null;
}
const { isEth, data, dataError, minBlock, minBlockError, total, totalError } = this.store;
const { isEth, data, dataError, total, totalError } = this.store;
return (
<Extras
@@ -215,8 +215,6 @@ class Transfer extends Component {
dataError={ dataError }
gasStore={ this.store.gasStore }
isEth={ isEth }
minBlock={ minBlock }
minBlockError={ minBlockError }
onChange={ this.store.onUpdateDetails }
total={ total }
totalError={ totalError }