Convert token input to BigNumber to handle decimals. (#12773)

* Fixes #12762

Adds a decimal length check for inputs and drops excess fractional part.
Another edgecase not accounted for is when a token's decimal precision is 0 and attempting sending decimals will result in omitting the fractional part.

* Change spies from sinon to jest and change onChange value to string.

* Adjust

* Remove sinon

* Add test for issue case

* DRY

* Simplify logic by using BigNumber

Co-authored-by: Dan Miller <danjm.com@gmail.com>
feature/default_network_editable
Thomas Huang 3 years ago committed by GitHub
parent 7929a92c66
commit d179344712
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      ui/components/ui/token-input/token-input.component.js
  2. 109
      ui/components/ui/token-input/token-input.component.test.js

@ -1,5 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import BigNumber from 'bignumber.js';
import UnitInput from '../unit-input'; import UnitInput from '../unit-input';
import CurrencyDisplay from '../currency-display'; import CurrencyDisplay from '../currency-display';
import { getWeiHexFromDecimalValue } from '../../../helpers/utils/conversions.util'; import { getWeiHexFromDecimalValue } from '../../../helpers/utils/conversions.util';
@ -7,6 +8,7 @@ import {
conversionUtil, conversionUtil,
multiplyCurrencies, multiplyCurrencies,
} from '../../../../shared/modules/conversion.utils'; } from '../../../../shared/modules/conversion.utils';
import { ETH } from '../../../helpers/constants/common'; import { ETH } from '../../../helpers/constants/common';
import { addHexPrefix } from '../../../../app/scripts/lib/util'; import { addHexPrefix } from '../../../../app/scripts/lib/util';
@ -81,7 +83,7 @@ export default class TokenInput extends PureComponent {
let newDecimalValue = decimalValue; let newDecimalValue = decimalValue;
if (decimals && applyDecimals) { if (decimals && applyDecimals) {
newDecimalValue = parseFloat(decimalValue).toFixed(decimals); newDecimalValue = new BigNumber(decimalValue, 10).toFixed(decimals);
} }
const multiplier = Math.pow(10, Number(decimals || 0)); const multiplier = Math.pow(10, Number(decimals || 0));

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { shallow, mount } from 'enzyme'; import { shallow, mount } from 'enzyme';
import sinon from 'sinon';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store'; import configureMockStore from 'redux-mock-store';
import UnitInput from '../unit-input'; import UnitInput from '../unit-input';
@ -207,12 +206,10 @@ describe('TokenInput Component', () => {
}); });
describe('handling actions', () => { describe('handling actions', () => {
const handleChangeSpy = sinon.spy(); const handleChangeSpy = jest.fn();
const handleBlurSpy = sinon.spy();
afterEach(() => { afterEach(() => {
handleChangeSpy.resetHistory(); handleChangeSpy.mockClear();
handleBlurSpy.resetHistory();
}); });
it('should call onChange on input changes with the hex value for ETH', () => { it('should call onChange on input changes with the hex value for ETH', () => {
@ -238,8 +235,7 @@ describe('TokenInput Component', () => {
); );
expect(wrapper).toHaveLength(1); expect(wrapper).toHaveLength(1);
expect(handleChangeSpy.callCount).toStrictEqual(0); expect(handleChangeSpy.mock.calls).toHaveLength(0);
expect(handleBlurSpy.callCount).toStrictEqual(0);
const tokenInputInstance = wrapper.find(TokenInput).at(0).instance(); const tokenInputInstance = wrapper.find(TokenInput).at(0).instance();
expect(tokenInputInstance.state.decimalValue).toStrictEqual(0); expect(tokenInputInstance.state.decimalValue).toStrictEqual(0);
@ -250,13 +246,13 @@ describe('TokenInput Component', () => {
const input = wrapper.find('input'); const input = wrapper.find('input');
expect(input.props().value).toStrictEqual(0); expect(input.props().value).toStrictEqual(0);
input.simulate('change', { target: { value: 1 } }); input.simulate('change', { target: { value: '1' } });
expect(handleChangeSpy.callCount).toStrictEqual(1); expect(handleChangeSpy.mock.calls).toHaveLength(1);
expect(handleChangeSpy.calledWith('2710')).toStrictEqual(true); expect(handleChangeSpy.mock.calls[0][0]).toStrictEqual('2710');
expect(wrapper.find('.currency-display-component').text()).toStrictEqual( expect(wrapper.find('.currency-display-component').text()).toStrictEqual(
'2ETH', '2ETH',
); );
expect(tokenInputInstance.state.decimalValue).toStrictEqual(1); expect(tokenInputInstance.state.decimalValue).toStrictEqual('1');
expect(tokenInputInstance.state.hexValue).toStrictEqual('2710'); expect(tokenInputInstance.state.hexValue).toStrictEqual('2710');
}); });
@ -285,8 +281,7 @@ describe('TokenInput Component', () => {
); );
expect(wrapper).toHaveLength(1); expect(wrapper).toHaveLength(1);
expect(handleChangeSpy.callCount).toStrictEqual(0); expect(handleChangeSpy.mock.calls).toHaveLength(0);
expect(handleBlurSpy.callCount).toStrictEqual(0);
const tokenInputInstance = wrapper.find(TokenInput).at(0).instance(); const tokenInputInstance = wrapper.find(TokenInput).at(0).instance();
expect(tokenInputInstance.state.decimalValue).toStrictEqual(0); expect(tokenInputInstance.state.decimalValue).toStrictEqual(0);
@ -297,13 +292,13 @@ describe('TokenInput Component', () => {
const input = wrapper.find('input'); const input = wrapper.find('input');
expect(input.props().value).toStrictEqual(0); expect(input.props().value).toStrictEqual(0);
input.simulate('change', { target: { value: 1 } }); input.simulate('change', { target: { value: '1' } });
expect(handleChangeSpy.callCount).toStrictEqual(1); expect(handleChangeSpy.mock.calls).toHaveLength(1);
expect(handleChangeSpy.calledWith('2710')).toStrictEqual(true); expect(handleChangeSpy.mock.calls[0][0]).toStrictEqual('2710');
expect(wrapper.find('.currency-display-component').text()).toStrictEqual( expect(wrapper.find('.currency-display-component').text()).toStrictEqual(
'$462.12USD', '$462.12USD',
); );
expect(tokenInputInstance.state.decimalValue).toStrictEqual(1); expect(tokenInputInstance.state.decimalValue).toStrictEqual('1');
expect(tokenInputInstance.state.hexValue).toStrictEqual('2710'); expect(tokenInputInstance.state.hexValue).toStrictEqual('2710');
}); });
@ -345,4 +340,84 @@ describe('TokenInput Component', () => {
); );
}); });
}); });
describe('Token Input Decimals Check', () => {
const handleChangeSpy = jest.fn();
afterEach(() => {
handleChangeSpy.mockClear();
});
it('should render incorrect hex onChange when input decimals is more than token decimals', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
};
const store = configureMockStore()(mockStore);
const wrapper = mount(
<Provider store={store}>
<TokenInput
onChange={handleChangeSpy}
token={{
address: '0x1',
decimals: 4,
symbol: 'ABC',
}}
tokenExchangeRates={{ '0x1': 2 }}
showFiat
currentCurrency="usd"
/>
</Provider>,
);
expect(wrapper).toHaveLength(1);
expect(handleChangeSpy.mock.calls).toHaveLength(0);
const input = wrapper.find('input');
expect(input.props().value).toStrictEqual(0);
input.simulate('change', { target: { value: '1.11111' } });
expect(handleChangeSpy.mock.calls).toHaveLength(1);
expect(handleChangeSpy.mock.calls[0][0]).toStrictEqual(
'2b67.1999999999999999999a',
);
});
it('should render correct hex onChange when input decimals is more than token decimals by omitting excess fractional part on blur', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
};
const store = configureMockStore()(mockStore);
const wrapper = mount(
<Provider store={store}>
<TokenInput
onChange={handleChangeSpy}
token={{
address: '0x1',
decimals: 4,
symbol: 'ABC',
}}
tokenExchangeRates={{ '0x1': 2 }}
showFiat
currentCurrency="usd"
/>
</Provider>,
);
expect(wrapper).toHaveLength(1);
const input = wrapper.find('input');
input.simulate('blur', { target: { value: '1.11111' } });
expect(handleChangeSpy.mock.calls).toHaveLength(1);
expect(handleChangeSpy.mock.calls[0][0]).toStrictEqual('2b67');
});
});
}); });

Loading…
Cancel
Save