From d1793447129685d60a43ce82e9b65f7a5c62b00f Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Wed, 24 Nov 2021 12:08:23 -0600 Subject: [PATCH] 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 --- .../ui/token-input/token-input.component.js | 4 +- .../token-input/token-input.component.test.js | 109 +++++++++++++++--- 2 files changed, 95 insertions(+), 18 deletions(-) diff --git a/ui/components/ui/token-input/token-input.component.js b/ui/components/ui/token-input/token-input.component.js index 763bc5824..a3bc88bf5 100644 --- a/ui/components/ui/token-input/token-input.component.js +++ b/ui/components/ui/token-input/token-input.component.js @@ -1,5 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; +import BigNumber from 'bignumber.js'; import UnitInput from '../unit-input'; import CurrencyDisplay from '../currency-display'; import { getWeiHexFromDecimalValue } from '../../../helpers/utils/conversions.util'; @@ -7,6 +8,7 @@ import { conversionUtil, multiplyCurrencies, } from '../../../../shared/modules/conversion.utils'; + import { ETH } from '../../../helpers/constants/common'; import { addHexPrefix } from '../../../../app/scripts/lib/util'; @@ -81,7 +83,7 @@ export default class TokenInput extends PureComponent { let newDecimalValue = decimalValue; if (decimals && applyDecimals) { - newDecimalValue = parseFloat(decimalValue).toFixed(decimals); + newDecimalValue = new BigNumber(decimalValue, 10).toFixed(decimals); } const multiplier = Math.pow(10, Number(decimals || 0)); diff --git a/ui/components/ui/token-input/token-input.component.test.js b/ui/components/ui/token-input/token-input.component.test.js index febb08ba9..ac8d55fe8 100644 --- a/ui/components/ui/token-input/token-input.component.test.js +++ b/ui/components/ui/token-input/token-input.component.test.js @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { shallow, mount } from 'enzyme'; -import sinon from 'sinon'; import { Provider } from 'react-redux'; import configureMockStore from 'redux-mock-store'; import UnitInput from '../unit-input'; @@ -207,12 +206,10 @@ describe('TokenInput Component', () => { }); describe('handling actions', () => { - const handleChangeSpy = sinon.spy(); - const handleBlurSpy = sinon.spy(); + const handleChangeSpy = jest.fn(); afterEach(() => { - handleChangeSpy.resetHistory(); - handleBlurSpy.resetHistory(); + handleChangeSpy.mockClear(); }); 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(handleChangeSpy.callCount).toStrictEqual(0); - expect(handleBlurSpy.callCount).toStrictEqual(0); + expect(handleChangeSpy.mock.calls).toHaveLength(0); const tokenInputInstance = wrapper.find(TokenInput).at(0).instance(); expect(tokenInputInstance.state.decimalValue).toStrictEqual(0); @@ -250,13 +246,13 @@ describe('TokenInput Component', () => { const input = wrapper.find('input'); expect(input.props().value).toStrictEqual(0); - input.simulate('change', { target: { value: 1 } }); - expect(handleChangeSpy.callCount).toStrictEqual(1); - expect(handleChangeSpy.calledWith('2710')).toStrictEqual(true); + input.simulate('change', { target: { value: '1' } }); + expect(handleChangeSpy.mock.calls).toHaveLength(1); + expect(handleChangeSpy.mock.calls[0][0]).toStrictEqual('2710'); expect(wrapper.find('.currency-display-component').text()).toStrictEqual( '2ETH', ); - expect(tokenInputInstance.state.decimalValue).toStrictEqual(1); + expect(tokenInputInstance.state.decimalValue).toStrictEqual('1'); expect(tokenInputInstance.state.hexValue).toStrictEqual('2710'); }); @@ -285,8 +281,7 @@ describe('TokenInput Component', () => { ); expect(wrapper).toHaveLength(1); - expect(handleChangeSpy.callCount).toStrictEqual(0); - expect(handleBlurSpy.callCount).toStrictEqual(0); + expect(handleChangeSpy.mock.calls).toHaveLength(0); const tokenInputInstance = wrapper.find(TokenInput).at(0).instance(); expect(tokenInputInstance.state.decimalValue).toStrictEqual(0); @@ -297,13 +292,13 @@ describe('TokenInput Component', () => { const input = wrapper.find('input'); expect(input.props().value).toStrictEqual(0); - input.simulate('change', { target: { value: 1 } }); - expect(handleChangeSpy.callCount).toStrictEqual(1); - expect(handleChangeSpy.calledWith('2710')).toStrictEqual(true); + input.simulate('change', { target: { value: '1' } }); + expect(handleChangeSpy.mock.calls).toHaveLength(1); + expect(handleChangeSpy.mock.calls[0][0]).toStrictEqual('2710'); expect(wrapper.find('.currency-display-component').text()).toStrictEqual( '$462.12USD', ); - expect(tokenInputInstance.state.decimalValue).toStrictEqual(1); + expect(tokenInputInstance.state.decimalValue).toStrictEqual('1'); 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( + + + , + ); + + 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( + + + , + ); + 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'); + }); + }); });