1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 18:00:18 +01:00

Adding warnings for excessive custom gas input (#10582)

Fixes MetaMask/metamask-extension#9811
This commit is contained in:
ryanml 2021-03-05 10:32:09 -07:00 committed by GitHub
parent 79a7199a2f
commit 45c076e232
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 174 additions and 6 deletions

View File

@ -763,6 +763,12 @@
"gasPrice": { "gasPrice": {
"message": "Gas Price (GWEI)" "message": "Gas Price (GWEI)"
}, },
"gasPriceExcessive": {
"message": "Your gas fee is set unnecessarily high. Consider lowering the amount."
},
"gasPriceExcessiveInput": {
"message": "Gas Price Is Excessive"
},
"gasPriceExtremelyLow": { "gasPriceExtremelyLow": {
"message": "Gas Price Extremely Low" "message": "Gas Price Extremely Low"
}, },

View File

@ -20,10 +20,12 @@ export default class AdvancedGasInputs extends Component {
isSpeedUp: PropTypes.bool, isSpeedUp: PropTypes.bool,
customGasLimitMessage: PropTypes.string, customGasLimitMessage: PropTypes.string,
minimumGasLimit: PropTypes.number, minimumGasLimit: PropTypes.number,
customPriceIsExcessive: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
minimumGasLimit: Number(MIN_GAS_LIMIT_DEC), minimumGasLimit: Number(MIN_GAS_LIMIT_DEC),
customPriceIsExcessive: false,
}; };
constructor(props) { constructor(props) {
@ -75,6 +77,7 @@ export default class AdvancedGasInputs extends Component {
customPriceIsSafe, customPriceIsSafe,
isSpeedUp, isSpeedUp,
gasPrice, gasPrice,
customPriceIsExcessive,
}) { }) {
const { t } = this.context; const { t } = this.context;
@ -93,6 +96,11 @@ export default class AdvancedGasInputs extends Component {
errorText: t('gasPriceExtremelyLow'), errorText: t('gasPriceExtremelyLow'),
errorType: 'warning', errorType: 'warning',
}; };
} else if (customPriceIsExcessive) {
return {
errorText: t('gasPriceExcessiveInput'),
errorType: 'error',
};
} }
return {}; return {};
@ -185,6 +193,7 @@ export default class AdvancedGasInputs extends Component {
isSpeedUp, isSpeedUp,
customGasLimitMessage, customGasLimitMessage,
minimumGasLimit, minimumGasLimit,
customPriceIsExcessive,
} = this.props; } = this.props;
const { gasPrice, gasLimit } = this.state; const { gasPrice, gasLimit } = this.state;
@ -196,6 +205,7 @@ export default class AdvancedGasInputs extends Component {
customPriceIsSafe, customPriceIsSafe,
isSpeedUp, isSpeedUp,
gasPrice, gasPrice,
customPriceIsExcessive,
}); });
const gasPriceErrorComponent = gasPriceErrorType ? ( const gasPriceErrorComponent = gasPriceErrorType ? (
<div <div

View File

@ -108,4 +108,15 @@ describe('Advanced Gas Inputs', function () {
assert.strictEqual(renderWarning.text(), 'gasPriceExtremelyLow'); assert.strictEqual(renderWarning.text(), 'gasPriceExtremelyLow');
}); });
it('errors when custom gas price is too excessive', function () {
wrapper.setProps({ customPriceIsExcessive: true });
const renderError = wrapper.find(
'.advanced-gas-inputs__gas-edit-row__error-text',
);
assert.strictEqual(renderError.length, 2);
assert.strictEqual(renderError.at(0).text(), 'gasPriceExcessiveInput');
});
}); });

View File

@ -18,6 +18,7 @@ export default class AdvancedTabContent extends Component {
isSpeedUp: PropTypes.bool, isSpeedUp: PropTypes.bool,
customGasLimitMessage: PropTypes.string, customGasLimitMessage: PropTypes.string,
minimumGasLimit: PropTypes.number, minimumGasLimit: PropTypes.number,
customPriceIsExcessive: PropTypes.bool.isRequired,
}; };
renderDataSummary(transactionFee) { renderDataSummary(transactionFee) {
@ -47,6 +48,7 @@ export default class AdvancedTabContent extends Component {
transactionFee, transactionFee,
customGasLimitMessage, customGasLimitMessage,
minimumGasLimit, minimumGasLimit,
customPriceIsExcessive,
} = this.props; } = this.props;
return ( return (
@ -64,6 +66,7 @@ export default class AdvancedTabContent extends Component {
isSpeedUp={isSpeedUp} isSpeedUp={isSpeedUp}
customGasLimitMessage={customGasLimitMessage} customGasLimitMessage={customGasLimitMessage}
minimumGasLimit={minimumGasLimit} minimumGasLimit={minimumGasLimit}
customPriceIsExcessive={customPriceIsExcessive}
/> />
</div> </div>
</div> </div>

View File

@ -35,6 +35,7 @@ export default class GasModalPageContainer extends Component {
isSpeedUp: PropTypes.bool, isSpeedUp: PropTypes.bool,
isRetry: PropTypes.bool, isRetry: PropTypes.bool,
disableSave: PropTypes.bool, disableSave: PropTypes.bool,
customPriceIsExcessive: PropTypes.bool.isRequired,
}; };
componentDidMount() { componentDidMount() {
@ -57,6 +58,7 @@ export default class GasModalPageContainer extends Component {
customPriceIsSafe, customPriceIsSafe,
isSpeedUp, isSpeedUp,
isRetry, isRetry,
customPriceIsExcessive,
infoRowProps: { transactionFee }, infoRowProps: { transactionFee },
} = this.props; } = this.props;
@ -71,6 +73,7 @@ export default class GasModalPageContainer extends Component {
customPriceIsSafe={customPriceIsSafe} customPriceIsSafe={customPriceIsSafe}
isSpeedUp={isSpeedUp} isSpeedUp={isSpeedUp}
isRetry={isRetry} isRetry={isRetry}
customPriceIsExcessive={customPriceIsExcessive}
/> />
); );
} }

View File

@ -37,6 +37,7 @@ import {
getTokenBalance, getTokenBalance,
getSendMaxModeState, getSendMaxModeState,
getAveragePriceEstimateInHexWEI, getAveragePriceEstimateInHexWEI,
isCustomPriceExcessive,
} from '../../../../selectors'; } from '../../../../selectors';
import { import {
@ -141,6 +142,7 @@ const mapStateToProps = (state, ownProps) => {
customGasTotal, customGasTotal,
newTotalFiat, newTotalFiat,
customPriceIsSafe: isCustomPriceSafe(state), customPriceIsSafe: isCustomPriceSafe(state),
customPriceIsExcessive: isCustomPriceExcessive(state),
maxModeOn, maxModeOn,
gasPriceButtonGroupProps: { gasPriceButtonGroupProps: {
buttonDataLoading, buttonDataLoading,

View File

@ -126,6 +126,7 @@ describe('gas-modal-page-container container', function () {
conversionRate: 50, conversionRate: 50,
customModalGasLimitInHex: 'aaaaaaaa', customModalGasLimitInHex: 'aaaaaaaa',
customModalGasPriceInHex: 'ffffffff', customModalGasPriceInHex: 'ffffffff',
customPriceIsExcessive: false,
customGasTotal: 'aaaaaaa955555556', customGasTotal: 'aaaaaaa955555556',
customPriceIsSafe: true, customPriceIsSafe: true,
gasPriceButtonGroupProps: { gasPriceButtonGroupProps: {

View File

@ -20,15 +20,17 @@ export default class SendContent extends Component {
isOwnedAccount: PropTypes.bool, isOwnedAccount: PropTypes.bool,
warning: PropTypes.string, warning: PropTypes.string,
error: PropTypes.string, error: PropTypes.string,
gasIsExcessive: PropTypes.bool.isRequired,
}; };
updateGas = (updateData) => this.props.updateGas(updateData); updateGas = (updateData) => this.props.updateGas(updateData);
render() { render() {
const { warning, error } = this.props; const { warning, error, gasIsExcessive } = this.props;
return ( return (
<PageContainerContent> <PageContainerContent>
<div className="send-v2__form"> <div className="send-v2__form">
{gasIsExcessive && this.renderError(true)}
{error && this.renderError()} {error && this.renderError()}
{warning && this.renderWarning()} {warning && this.renderWarning()}
{this.maybeRenderAddContact()} {this.maybeRenderAddContact()}
@ -77,13 +79,13 @@ export default class SendContent extends Component {
); );
} }
renderError() { renderError(gasError = false) {
const { t } = this.context; const { t } = this.context;
const { error } = this.props; const { error } = this.props;
return ( return (
<Dialog type="error" className="send__error-dialog"> <Dialog type="error" className="send__error-dialog">
{t(error)} {gasError ? t('gasPriceExcessive') : t(error)}
</Dialog> </Dialog>
); );
} }

View File

@ -58,6 +58,7 @@ export default class SendTransactionScreen extends Component {
qrCodeDetected: PropTypes.func.isRequired, qrCodeDetected: PropTypes.func.isRequired,
qrCodeData: PropTypes.object, qrCodeData: PropTypes.object,
sendTokenAddress: PropTypes.string, sendTokenAddress: PropTypes.string,
gasIsExcessive: PropTypes.bool.isRequired,
}; };
static contextTypes = { static contextTypes = {
@ -382,7 +383,7 @@ export default class SendTransactionScreen extends Component {
} }
renderSendContent() { renderSendContent() {
const { history, showHexData } = this.props; const { history, showHexData, gasIsExcessive } = this.props;
const { toWarning, toError } = this.state; const { toWarning, toError } = this.state;
return [ return [
@ -394,6 +395,7 @@ export default class SendTransactionScreen extends Component {
showHexData={showHexData} showHexData={showHexData}
warning={toWarning} warning={toWarning}
error={toError} error={toError}
gasIsExcessive={gasIsExcessive}
/>, />,
<SendFooter key="send-footer" history={history} />, <SendFooter key="send-footer" history={history} />,
]; ];

View File

@ -23,6 +23,7 @@ import {
getSelectedAddress, getSelectedAddress,
getAddressBook, getAddressBook,
getSendTokenAddress, getSendTokenAddress,
isCustomPriceExcessive,
} from '../../selectors'; } from '../../selectors';
import { import {
@ -67,6 +68,7 @@ function mapStateToProps(state) {
tokenBalance: getTokenBalance(state), tokenBalance: getTokenBalance(state),
tokenContract: getSendTokenContract(state), tokenContract: getSendTokenContract(state),
sendTokenAddress: getSendTokenAddress(state), sendTokenAddress: getSendTokenAddress(state),
gasIsExcessive: isCustomPriceExcessive(state, true),
}; };
} }

View File

@ -9,7 +9,12 @@ import { formatETHFee } from '../helpers/utils/formatters';
import { calcGasTotal } from '../pages/send/send.utils'; import { calcGasTotal } from '../pages/send/send.utils';
import { GAS_ESTIMATE_TYPES } from '../helpers/constants/common'; import { GAS_ESTIMATE_TYPES } from '../helpers/constants/common';
import { getCurrentCurrency, getIsMainnet, getPreferences } from '.'; import {
getCurrentCurrency,
getIsMainnet,
getPreferences,
getGasPrice,
} from '.';
const NUMBER_OF_DECIMALS_SM_BTNS = 5; const NUMBER_OF_DECIMALS_SM_BTNS = 5;
@ -31,7 +36,7 @@ export function getAveragePriceEstimateInHexWEI(state) {
} }
export function getFastPriceEstimateInHexWEI(state) { export function getFastPriceEstimateInHexWEI(state) {
const fastPriceEstimate = state.gas.basicEstimates.fast; const fastPriceEstimate = getFastPriceEstimate(state);
return getGasPriceInHexWei(fastPriceEstimate || '0x0'); return getGasPriceInHexWei(fastPriceEstimate || '0x0');
} }
@ -55,6 +60,16 @@ export function getSafeLowEstimate(state) {
return safeLow; return safeLow;
} }
export function getFastPriceEstimate(state) {
const {
gas: {
basicEstimates: { fast },
},
} = state;
return fast;
}
export function isCustomPriceSafe(state) { export function isCustomPriceSafe(state) {
const safeLow = getSafeLowEstimate(state); const safeLow = getSafeLowEstimate(state);
@ -81,6 +96,31 @@ export function isCustomPriceSafe(state) {
return customPriceSafe; return customPriceSafe;
} }
export function isCustomPriceExcessive(state, checkSend = false) {
const customPrice = checkSend ? getGasPrice(state) : getCustomGasPrice(state);
const fastPrice = getFastPriceEstimate(state);
if (!customPrice || !fastPrice) {
return false;
}
// Custom gas should be considered excessive when it is 1.5 times greater than the fastest estimate.
const customPriceExcessive = conversionGreaterThan(
{
value: customPrice,
fromNumericBase: 'hex',
fromDenomination: 'WEI',
toDenomination: 'GWEI',
},
{
fromNumericBase: 'dec',
value: Math.floor(fastPrice * 1.5),
},
);
return customPriceExcessive;
}
export function basicPriceEstimateToETHTotal( export function basicPriceEstimateToETHTotal(
estimate, estimate,
gasLimit, gasLimit,

View File

@ -7,6 +7,7 @@ const {
getRenderableBasicEstimateData, getRenderableBasicEstimateData,
getRenderableEstimateDataForSmallButtonsFromGWEI, getRenderableEstimateDataForSmallButtonsFromGWEI,
isCustomPriceSafe, isCustomPriceSafe,
isCustomPriceExcessive,
} = proxyquire('../custom-gas', {}); } = proxyquire('../custom-gas', {});
describe('custom-gas selectors', function () { describe('custom-gas selectors', function () {
@ -55,6 +56,91 @@ describe('custom-gas selectors', function () {
}); });
}); });
describe('isCustomPriceExcessive()', function () {
it('should return false for gas.customData.price null', function () {
const mockState = {
gas: {
customData: { price: null },
basicEstimates: { fast: 150 },
},
};
assert.strictEqual(isCustomPriceExcessive(mockState), false);
});
it('should return false gas.basicEstimates.fast undefined', function () {
const mockState = {
gas: {
customData: { price: '0x77359400' },
basicEstimates: { fast: undefined },
},
};
assert.strictEqual(isCustomPriceExcessive(mockState), false);
});
it('should return false gas.basicEstimates.price 0x205d0bae00 (139)', function () {
const mockState = {
gas: {
customData: { price: '0x205d0bae00' },
basicEstimates: { fast: 139 },
},
};
assert.strictEqual(isCustomPriceExcessive(mockState), false);
});
it('should return false gas.basicEstimates.price 0x1bf08eb000 (120)', function () {
const mockState = {
gas: {
customData: { price: '0x1bf08eb000' },
basicEstimates: { fast: 139 },
},
};
assert.strictEqual(isCustomPriceExcessive(mockState), false);
});
it('should return false gas.basicEstimates.price 0x28bed01600 (175)', function () {
const mockState = {
gas: {
customData: { price: '0x28bed01600' },
basicEstimates: { fast: 139 },
},
};
assert.strictEqual(isCustomPriceExcessive(mockState), false);
});
it('should return true gas.basicEstimates.price 0x30e4f9b400 (210)', function () {
const mockState = {
gas: {
customData: { price: '0x30e4f9b400' },
basicEstimates: { fast: 139 },
},
};
assert.strictEqual(isCustomPriceExcessive(mockState), true);
});
it('should return false gas.basicEstimates.price 0x28bed01600 (175) (checkSend=true)', function () {
const mockState = {
metamask: {
send: {
gasPrice: '0x28bed0160',
},
},
gas: {
customData: { price: null },
basicEstimates: { fast: 139 },
},
};
assert.strictEqual(isCustomPriceExcessive(mockState, true), false);
});
it('should return true gas.basicEstimates.price 0x30e4f9b400 (210) (checkSend=true)', function () {
const mockState = {
metamask: {
send: {
gasPrice: '0x30e4f9b400',
},
},
gas: {
customData: { price: null },
basicEstimates: { fast: 139 },
},
};
assert.strictEqual(isCustomPriceExcessive(mockState, true), true);
});
});
describe('getCustomGasLimit()', function () { describe('getCustomGasLimit()', function () {
it('should return gas.customData.limit', function () { it('should return gas.customData.limit', function () {
const mockState = { gas: { customData: { limit: 'mockLimit' } } }; const mockState = { gas: { customData: { limit: 'mockLimit' } } };