Gas calculation & limit errors
This commit is contained in:
		
							parent
							
								
									3f2ba96e97
								
							
						
					
					
						commit
						e69b7f66a3
					
				| @ -36,6 +36,7 @@ export default class DetailsStep extends Component { | ||||
|     onFuncChange: PropTypes.func, | ||||
|     values: PropTypes.array.isRequired, | ||||
|     valuesError: PropTypes.array.isRequired, | ||||
|     warning: PropTypes.string, | ||||
|     onValueChange: PropTypes.func.isRequired | ||||
|   } | ||||
| 
 | ||||
| @ -44,6 +45,7 @@ export default class DetailsStep extends Component { | ||||
| 
 | ||||
|     return ( | ||||
|       <Form> | ||||
|         { this.renderWarning() } | ||||
|         <AddressSelect | ||||
|           label='from account' | ||||
|           hint='the account to transact with' | ||||
| @ -178,6 +180,20 @@ export default class DetailsStep extends Component { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   renderWarning () { | ||||
|     const { warning } = this.props; | ||||
| 
 | ||||
|     if (!warning) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.warning }> | ||||
|         { warning } | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   onFuncChange = (event, index, signature) => { | ||||
|     const { contract, onFuncChange } = this.props; | ||||
| 
 | ||||
|  | ||||
| @ -33,3 +33,11 @@ | ||||
| .txhash { | ||||
|   word-break: break-all; | ||||
| } | ||||
| 
 | ||||
| .warning { | ||||
|   border-radius: 0.5em; | ||||
|   background: #f80; | ||||
|   color: white; | ||||
|   padding: 0.75em; | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| @ -15,17 +15,21 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui'; | ||||
| import { MAX_GAS_ESTIMATION } from '../../util/constants'; | ||||
| import { validateAddress, validateUint } from '../../util/validation'; | ||||
| 
 | ||||
| import DetailsStep from './DetailsStep'; | ||||
| 
 | ||||
| import ERRORS from '../Transfer/errors'; | ||||
| import { ERROR_CODES } from '../../api/transport/error'; | ||||
| 
 | ||||
| export default class ExecuteContract extends Component { | ||||
| class ExecuteContract extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object.isRequired, | ||||
|     store: PropTypes.object.isRequired | ||||
| @ -36,6 +40,7 @@ export default class ExecuteContract extends Component { | ||||
|     fromAddress: PropTypes.string, | ||||
|     accounts: PropTypes.object, | ||||
|     contract: PropTypes.object, | ||||
|     gasLimit: PropTypes.object.isRequired, | ||||
|     onClose: PropTypes.func.isRequired, | ||||
|     onFromAddressChange: PropTypes.func.isRequired | ||||
|   } | ||||
| @ -46,6 +51,8 @@ export default class ExecuteContract extends Component { | ||||
|     fromAddressError: null, | ||||
|     func: null, | ||||
|     funcError: null, | ||||
|     gas: null, | ||||
|     gasLimitError: null, | ||||
|     values: [], | ||||
|     valuesError: [], | ||||
|     step: 0, | ||||
| @ -119,7 +126,7 @@ export default class ExecuteContract extends Component { | ||||
| 
 | ||||
|   renderStep () { | ||||
|     const { onFromAddressChange } = this.props; | ||||
|     const { step, busyState, txhash, rejected } = this.state; | ||||
|     const { step, busyState, gasLimitError, txhash, rejected } = this.state; | ||||
| 
 | ||||
|     if (rejected) { | ||||
|       return ( | ||||
| @ -135,6 +142,7 @@ export default class ExecuteContract extends Component { | ||||
|         <DetailsStep | ||||
|           { ...this.props } | ||||
|           { ...this.state } | ||||
|           warning={ gasLimitError } | ||||
|           onAmountChange={ this.onAmountChange } | ||||
|           onFromAddressChange={ onFromAddressChange } | ||||
|           onFuncChange={ this.onFuncChange } | ||||
| @ -156,6 +164,7 @@ export default class ExecuteContract extends Component { | ||||
|   } | ||||
| 
 | ||||
|   onAmountChange = (amount) => { | ||||
|     this.estimateGas(); | ||||
|     this.setState({ amount }); | ||||
|   } | ||||
| 
 | ||||
| @ -179,6 +188,7 @@ export default class ExecuteContract extends Component { | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     this.estimateGas(); | ||||
|     this.setState({ | ||||
|       func, | ||||
|       values | ||||
| @ -208,17 +218,54 @@ export default class ExecuteContract extends Component { | ||||
|     values[index] = value; | ||||
|     valuesError[index] = valueError; | ||||
| 
 | ||||
|     if (!valueError) { | ||||
|       this.estimateGas(); | ||||
|     } | ||||
| 
 | ||||
|     this.setState({ | ||||
|       values: [].concat(values), | ||||
|       valuesError: [].concat(valuesError) | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   estimateGas = () => { | ||||
|     const { api } = this.context; | ||||
|     const { fromAddress, gasLimit } = this.props; | ||||
|     const { amount, func, values } = this.state; | ||||
|     const options = { | ||||
|       gas: MAX_GAS_ESTIMATION, | ||||
|       from: fromAddress, | ||||
|       value: api.util.toWei(amount || 0) | ||||
|     }; | ||||
| 
 | ||||
|     func | ||||
|       .estimateGas(options, values) | ||||
|       .then((gasEst) => { | ||||
|         const gas = gasEst.mul(1.2); | ||||
|         let gasLimitError = null; | ||||
| 
 | ||||
|         if (gas.gte(MAX_GAS_ESTIMATION)) { | ||||
|           gasLimitError = ERRORS.gasException; | ||||
|         } else if (gas.gt(gasLimit)) { | ||||
|           gasLimitError = ERRORS.gasBlockLimit; | ||||
|         } | ||||
| 
 | ||||
|         this.setState({ | ||||
|           gas, | ||||
|           gasLimitError | ||||
|         }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('estimateGas', error); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   postTransaction = () => { | ||||
|     const { api, store } = this.context; | ||||
|     const { fromAddress } = this.props; | ||||
|     const { amount, func, values } = this.state; | ||||
|     const options = { | ||||
|       gas: MAX_GAS_ESTIMATION, | ||||
|       from: fromAddress, | ||||
|       value: api.util.toWei(amount || 0) | ||||
|     }; | ||||
| @ -237,13 +284,13 @@ export default class ExecuteContract extends Component { | ||||
| 
 | ||||
|         return api | ||||
|           .pollMethod('parity_checkRequest', requestId) | ||||
|           .catch((e) => { | ||||
|             if (e.code === ERROR_CODES.REQUEST_REJECTED) { | ||||
|           .catch((error) => { | ||||
|             if (error.code === ERROR_CODES.REQUEST_REJECTED) { | ||||
|               this.setState({ rejected: true }); | ||||
|               return false; | ||||
|             } | ||||
| 
 | ||||
|             throw e; | ||||
|             throw error; | ||||
|           }); | ||||
|       }) | ||||
|       .then((txhash) => { | ||||
| @ -255,3 +302,18 @@ export default class ExecuteContract extends Component { | ||||
|       }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (state) { | ||||
|   const { gasLimit } = state.status; | ||||
| 
 | ||||
|   return { gasLimit }; | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({}, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|   mapStateToProps, | ||||
|   mapDispatchToProps | ||||
| )(ExecuteContract); | ||||
|  | ||||
| @ -19,7 +19,9 @@ const ERRORS = { | ||||
|   invalidAddress: 'the supplied address is an invalid network address', | ||||
|   invalidAmount: 'the supplied amount should be a valid positive number', | ||||
|   invalidDecimals: 'the supplied amount exceeds the allowed decimals', | ||||
|   largeAmount: 'the transaction total is higher than the available balance' | ||||
|   largeAmount: 'the transaction total is higher than the available balance', | ||||
|   gasException: 'the transaction indicates a thrown exception', | ||||
|   gasBlockLimit: 'the transaction will exceed the block gas limit' | ||||
| }; | ||||
| 
 | ||||
| export default ERRORS; | ||||
|  | ||||
| @ -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/>. | ||||
| */ | ||||
| 
 | ||||
| .info { | ||||
|   line-height: 1.618em; | ||||
|   width: 100%; | ||||
| @ -151,3 +152,11 @@ | ||||
| .gasPriceDesc { | ||||
|   font-size: 0.9em; | ||||
| } | ||||
| 
 | ||||
| .warning { | ||||
|   border-radius: 0.5em; | ||||
|   background: #f80; | ||||
|   color: white; | ||||
|   padding: 0.75em; | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| @ -16,12 +16,15 @@ | ||||
| 
 | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; | ||||
| import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; | ||||
| 
 | ||||
| import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui'; | ||||
| import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '../../util/constants'; | ||||
| 
 | ||||
| import Details from './Details'; | ||||
| import Extras from './Extras'; | ||||
| @ -30,8 +33,6 @@ import styles from './transfer.css'; | ||||
| 
 | ||||
| import { ERROR_CODES } from '../../api/transport/error'; | ||||
| 
 | ||||
| const DEFAULT_GAS = '21000'; | ||||
| const DEFAULT_GASPRICE = '20000000000'; | ||||
| const TITLES = { | ||||
|   transfer: 'transfer details', | ||||
|   sending: 'sending', | ||||
| @ -42,7 +43,7 @@ const TITLES = { | ||||
| const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete]; | ||||
| const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete]; | ||||
| 
 | ||||
| export default class Transfer extends Component { | ||||
| class Transfer extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object.isRequired, | ||||
|     store: PropTypes.object.isRequired | ||||
| @ -52,6 +53,7 @@ export default class Transfer extends Component { | ||||
|     account: PropTypes.object, | ||||
|     balance: PropTypes.object, | ||||
|     balances: PropTypes.object, | ||||
|     gasLimit: PropTypes.object.isRequired, | ||||
|     images: PropTypes.object.isRequired, | ||||
|     onClose: PropTypes.func | ||||
|   } | ||||
| @ -64,6 +66,7 @@ export default class Transfer extends Component { | ||||
|     gas: DEFAULT_GAS, | ||||
|     gasEst: '0', | ||||
|     gasError: null, | ||||
|     gasLimitError: null, | ||||
|     gasPrice: DEFAULT_GASPRICE, | ||||
|     gasPriceHistogram: {}, | ||||
|     gasPriceError: null, | ||||
| @ -103,6 +106,7 @@ export default class Transfer extends Component { | ||||
|         visible | ||||
|         scroll | ||||
|       > | ||||
|         { this.renderWarning() } | ||||
|         { this.renderPage() } | ||||
|       </Modal> | ||||
|     ); | ||||
| @ -264,6 +268,20 @@ export default class Transfer extends Component { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   renderWarning () { | ||||
|     const { gasLimitError } = this.state; | ||||
| 
 | ||||
|     if (!gasLimitError) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.warning }> | ||||
|         { gasLimitError } | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   isValid () { | ||||
|     const detailsValid = !this.state.recipientError && !this.state.valueError && !this.state.totalError; | ||||
|     const extrasValid = !this.state.gasError && !this.state.gasPriceError && !this.state.totalError; | ||||
| @ -519,6 +537,7 @@ export default class Transfer extends Component { | ||||
| 
 | ||||
|     return token.contract.instance.transfer | ||||
|       .estimateGas({ | ||||
|         gas: MAX_GAS_ESTIMATION, | ||||
|         from: account.address, | ||||
|         to: token.address | ||||
|       }, [ | ||||
| @ -532,6 +551,7 @@ export default class Transfer extends Component { | ||||
|     const { account } = this.props; | ||||
|     const { data, recipient, value } = this.state; | ||||
|     const options = { | ||||
|       gas: MAX_GAS_ESTIMATION, | ||||
|       from: account.address, | ||||
|       to: recipient, | ||||
|       value: api.util.toWei(value || 0) | ||||
| @ -552,19 +572,29 @@ export default class Transfer extends Component { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const { gasLimit } = this.props; | ||||
| 
 | ||||
|     (this.state.isEth | ||||
|       ? this._estimateGasEth() | ||||
|       : this._estimateGasToken() | ||||
|     ).then((_value) => { | ||||
|       let gas = _value; | ||||
|     ).then((gasEst) => { | ||||
|       let gas = gasEst; | ||||
|       let gasLimitError = null; | ||||
| 
 | ||||
|       if (gas.gt(DEFAULT_GAS)) { | ||||
|         gas = gas.mul(1.2); | ||||
|       } | ||||
| 
 | ||||
|       if (gas.gte(MAX_GAS_ESTIMATION)) { | ||||
|         gasLimitError = ERRORS.gasException; | ||||
|       } else if (gas.gt(gasLimit)) { | ||||
|         gasLimitError = ERRORS.gasBlockLimit; | ||||
|       } | ||||
| 
 | ||||
|       this.setState({ | ||||
|         gas: gas.toFixed(0), | ||||
|         gasEst: _value.toFormat() | ||||
|         gasEst: gasEst.toFormat(), | ||||
|         gasLimitError | ||||
|       }, this.recalculate); | ||||
|     }) | ||||
|     .catch((error) => { | ||||
| @ -649,3 +679,18 @@ export default class Transfer extends Component { | ||||
|     store.dispatch({ type: 'newError', error }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (state) { | ||||
|   const { gasLimit } = state.status; | ||||
| 
 | ||||
|   return { gasLimit }; | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({}, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|   mapStateToProps, | ||||
|   mapDispatchToProps | ||||
| )(Transfer); | ||||
|  | ||||
							
								
								
									
										26
									
								
								js/src/util/constants.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								js/src/util/constants.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| const DEFAULT_GAS = '21000'; | ||||
| const DEFAULT_GASPRICE = '20000000000'; | ||||
| 
 | ||||
| const MAX_GAS_ESTIMATION = '50000000'; | ||||
| 
 | ||||
| export default { | ||||
|   DEFAULT_GAS, | ||||
|   DEFAULT_GASPRICE, | ||||
|   MAX_GAS_ESTIMATION | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user