From 8fe664c17165dc456432baf6aa94d4aefbb5ef55 Mon Sep 17 00:00:00 2001 From: HenryNguyen5 Date: Sun, 12 Nov 2017 14:45:52 -0500 Subject: [PATCH] Replace bignumber.js with bn.js (#319) * Add definition file for bn.js * Remove types-bn * make isBN a static property * progress commit -- swap out bignumber.js for bn.js * Swap out bignumber for bn in vendor * Change modn to number return * Start to strip out units lib for a string manipulation based lib * Convert codebase to only base units * Get rid of useless component * Handle only wei in values * Use unit conversion in sidebar * Automatically strip hex prefix, and handle decimal edge case * Handle base 16 wei in transactions * Make a render callback component for dealing with unit conversion * Switch contracts to use bn.js, and get transaction values from signedTx instead of state * Get send transaction working with bn.js * Remove redundant hex stripping, return base value of tokens * Cleanup unit file * Re-implement toFixed for strings * Use formatNumber in codebase * Cleanup code * Undo package test changes * Update snapshot and remove console logs * Use TokenValue / Wei more consistently where applicable * Add typing to deterministicWallets, fix confirmation modal, make UnitDisplay more flexible * Clean up prop handling in UnitDisplay * Change instanceof to typeof check, change boolean of displayBalance * Fix tsc errors * Fix token row displaying wrong decimals * Fix deterministic modal token display * Handle hex and non hex strings automatically in BN conversion * Fix handling of strings and numbers for BN * add web3 fixes & comments * Display short balances on deterministic modals * add more tests, fix rounding * Add spacer to balance sidebar network name * Fix tsc error --- common/actions/config/constants.ts | 2 +- .../deterministicWallets/actionTypes.ts | 19 +- common/actions/wallet/actionCreators.ts | 5 +- common/actions/wallet/actionTypes.ts | 5 +- .../components/BalanceSidebar/AccountInfo.tsx | 61 ++-- .../BalanceSidebar/EquivalentValues.tsx | 18 +- .../BalanceSidebar/TokenBalances/TokenRow.tsx | 26 +- .../BalanceSidebar/TokenBalances/index.tsx | 20 +- common/components/BalanceSidebar/index.tsx | 4 +- common/components/Header/index.tsx | 10 +- .../DeterministicWalletsModal.tsx | 29 +- common/components/WalletDecrypt/Keystore.tsx | 6 +- common/components/renderCbs/UnitConverter.tsx | 60 ++++ common/components/renderCbs/index.ts | 1 + common/components/ui/UnitDisplay.tsx | 73 ++++ common/components/ui/index.ts | 1 + .../Deploy/components/DeployHoc/index.tsx | 24 +- .../Deploy/components/DeployHoc/types.ts | 4 +- .../components/InteractExplorer/index.tsx | 31 +- .../Contracts/components/Interact/index.tsx | 25 +- .../Tabs/Contracts/components/TxCompare.tsx | 23 +- .../Tabs/Contracts/components/withTx.tsx | 9 +- .../components/AmountField.tsx | 73 ++-- .../components/ConfirmationModal.tsx | 150 +++++---- .../containers/Tabs/SendTransaction/index.tsx | 76 +++-- common/derivation-checker.ts | 4 +- common/libs/contracts/ABIFunction.ts | 15 +- common/libs/erc20.ts | 8 +- common/libs/nodes/INode.ts | 9 +- common/libs/nodes/rpc/index.ts | 27 +- common/libs/nodes/rpc/utils.ts | 4 +- common/libs/nodes/web3/index.ts | 40 +-- common/libs/transaction.ts | 73 ++-- common/libs/transaction/transaction.ts | 312 ++++++++++++++++++ common/libs/units.ts | 131 ++++---- common/libs/values.ts | 10 +- common/libs/wallet/deterministic/trezor.ts | 5 +- common/reducers/deterministicWallets.ts | 5 +- common/reducers/wallet.ts | 11 +- common/sagas/config.ts | 6 +- common/sagas/deterministicWallets.ts | 5 +- common/sagas/wallet.tsx | 2 +- common/selectors/wallet.ts | 10 +- common/typescript/bn.d.ts | 2 +- common/utils/formatters.ts | 57 +++- package.json | 3 +- spec/libs/erc20.spec.ts | 9 +- spec/libs/nodes/rpc/rpc.spec.ts | 8 +- spec/libs/units.spec.ts | 129 +++++--- .../SendTransaction.spec.tsx.snap | 7 +- spec/reducers/wallet.spec.ts | 4 +- spec/utils/formatters.spec.ts | 42 ++- 52 files changed, 1129 insertions(+), 564 deletions(-) create mode 100644 common/components/renderCbs/UnitConverter.tsx create mode 100644 common/components/renderCbs/index.ts create mode 100644 common/components/ui/UnitDisplay.tsx create mode 100644 common/libs/transaction/transaction.ts diff --git a/common/actions/config/constants.ts b/common/actions/config/constants.ts index 1e1dc918..3e333e64 100644 --- a/common/actions/config/constants.ts +++ b/common/actions/config/constants.ts @@ -6,5 +6,5 @@ export enum TypeKeys { CONFIG_TOGGLE_OFFLINE = 'CONFIG_TOGGLE_OFFLINE', CONFIG_FORCE_OFFLINE = 'CONFIG_FORCE_OFFLINE', CONFIG_POLL_OFFLINE_STATUS = 'CONFIG_POLL_OFFLINE_STATUS', - CONFIG_NODE_WEB3_UNSET = 'CONFIG_NODE_WEB3_UNSET', + CONFIG_NODE_WEB3_UNSET = 'CONFIG_NODE_WEB3_UNSET' } diff --git a/common/actions/deterministicWallets/actionTypes.ts b/common/actions/deterministicWallets/actionTypes.ts index bc601b50..bf20e9fd 100644 --- a/common/actions/deterministicWallets/actionTypes.ts +++ b/common/actions/deterministicWallets/actionTypes.ts @@ -1,14 +1,19 @@ -import { BigNumber } from 'bignumber.js'; +import { TokenValue, Wei } from 'libs/units'; -export interface TokenValues { - [key: string]: BigNumber; +export interface ITokenData { + value: TokenValue; + decimal: number; +} + +export interface ITokenValues { + [key: string]: ITokenData | null; } export interface DeterministicWalletData { index: number; address: string; - value?: BigNumber; - tokenValues: TokenValues; + value?: TokenValue; + tokenValues: ITokenValues; } /*** Get determinstic wallets ***/ @@ -39,8 +44,8 @@ export interface SetDesiredTokenAction { /*** Set wallet values ***/ export interface UpdateDeterministicWalletArgs { address: string; - value?: BigNumber; - tokenValues?: TokenValues; + value?: Wei; + tokenValues?: ITokenValues; index?: any; } diff --git a/common/actions/wallet/actionCreators.ts b/common/actions/wallet/actionCreators.ts index b9241918..e104d779 100644 --- a/common/actions/wallet/actionCreators.ts +++ b/common/actions/wallet/actionCreators.ts @@ -1,5 +1,4 @@ -import { BigNumber } from 'bignumber.js'; -import { Wei } from 'libs/units'; +import { Wei, TokenValue } from 'libs/units'; import { IWallet } from 'libs/wallet/IWallet'; import * as types from './actionTypes'; import { TypeKeys } from './constants'; @@ -58,7 +57,7 @@ export function setBalance(value: Wei): types.SetBalanceAction { export type TSetTokenBalances = typeof setTokenBalances; export function setTokenBalances(payload: { - [key: string]: BigNumber; + [key: string]: TokenValue; }): types.SetTokenBalancesAction { return { type: TypeKeys.WALLET_SET_TOKEN_BALANCES, diff --git a/common/actions/wallet/actionTypes.ts b/common/actions/wallet/actionTypes.ts index a64454f5..f44c280e 100644 --- a/common/actions/wallet/actionTypes.ts +++ b/common/actions/wallet/actionTypes.ts @@ -1,5 +1,4 @@ -import { BigNumber } from 'bignumber.js'; -import { Wei } from 'libs/units'; +import { Wei, TokenValue } from 'libs/units'; import { IWallet } from 'libs/wallet/IWallet'; import { TypeKeys } from './constants'; @@ -43,7 +42,7 @@ export interface SetBalanceAction { export interface SetTokenBalancesAction { type: TypeKeys.WALLET_SET_TOKEN_BALANCES; payload: { - [key: string]: BigNumber; + [key: string]: TokenValue; }; } diff --git a/common/components/BalanceSidebar/AccountInfo.tsx b/common/components/BalanceSidebar/AccountInfo.tsx index d2cec006..757474bb 100644 --- a/common/components/BalanceSidebar/AccountInfo.tsx +++ b/common/components/BalanceSidebar/AccountInfo.tsx @@ -1,15 +1,14 @@ import { TFetchCCRates } from 'actions/rates'; -import { Identicon } from 'components/ui'; +import { Identicon, UnitDisplay } from 'components/ui'; import { NetworkConfig } from 'config/data'; -import { Ether } from 'libs/units'; import { IWallet } from 'libs/wallet'; +import { Wei } from 'libs/units'; import React from 'react'; import translate from 'translations'; -import { formatNumber } from 'utils/formatters'; import './AccountInfo.scss'; interface Props { - balance: Ether; + balance: Wei; wallet: IWallet; network: NetworkConfig; fetchCCRates: TFetchCCRates; @@ -54,7 +53,7 @@ export default class AccountInfo extends React.Component { public render() { const { network, balance } = this.props; const { blockExplorer, tokenExplorer } = network; - const { address } = this.state; + const { address, showLongBalance } = this.state; return (
@@ -80,9 +79,11 @@ export default class AccountInfo extends React.Component { className="AccountInfo-list-item-clickable mono wrap" onClick={this.toggleShowLongBalance} > - {this.state.showLongBalance - ? balance ? balance.toString() : '???' - : balance ? formatNumber(balance.amount) : '???'} + {` ${network.name}`} @@ -90,28 +91,28 @@ export default class AccountInfo extends React.Component {
{(!!blockExplorer || !!tokenExplorer) && ( -
-
- {translate('sidebar_TransHistory')} -
- -
- )} +
+
+ {translate('sidebar_TransHistory')} +
+ +
+ )} ); } diff --git a/common/components/BalanceSidebar/EquivalentValues.tsx b/common/components/BalanceSidebar/EquivalentValues.tsx index 3b30bb35..865a238b 100644 --- a/common/components/BalanceSidebar/EquivalentValues.tsx +++ b/common/components/BalanceSidebar/EquivalentValues.tsx @@ -1,13 +1,13 @@ -import { Ether } from 'libs/units'; +import { Wei } from 'libs/units'; import React from 'react'; import translate from 'translations'; -import { formatNumber } from 'utils/formatters'; import './EquivalentValues.scss'; import { State } from 'reducers/rates'; import { symbols } from 'actions/rates'; +import { UnitDisplay } from 'components/ui'; interface Props { - balance?: Ether; + balance?: Wei; rates?: State['rates']; ratesError?: State['ratesError']; } @@ -33,9 +33,15 @@ export default class EquivalentValues extends React.Component { {' '} - {balance - ? formatNumber(balance.amount.times(rates[key])) - : '???'} + {balance ? ( + + ) : ( + '???' + )} ); diff --git a/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx b/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx index b8165dd1..d7740b62 100644 --- a/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx +++ b/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx @@ -1,13 +1,14 @@ import removeIcon from 'assets/images/icon-remove.svg'; -import { BigNumber } from 'bignumber.js'; import React from 'react'; -import { formatNumber } from 'utils/formatters'; +import { TokenValue } from 'libs/units'; +import { UnitDisplay } from 'components/ui'; import './TokenRow.scss'; interface Props { - balance: BigNumber; + balance: TokenValue; symbol: string; custom?: boolean; + decimal: number; onRemove(symbol: string): void; } interface State { @@ -18,9 +19,11 @@ export default class TokenRow extends React.Component { public state = { showLongBalance: false }; + public render() { - const { balance, symbol, custom } = this.props; + const { balance, symbol, custom, decimal } = this.props; const { showLongBalance } = this.state; + return ( { title={`${balance.toString()} (Double-Click)`} onDoubleClick={this.toggleShowLongBalance} > - {!!custom && + {!!custom && ( } + /> + )} - {showLongBalance ? balance.toString() : formatNumber(balance)} + - - {symbol} - + {symbol} ); } diff --git a/common/components/BalanceSidebar/TokenBalances/index.tsx b/common/components/BalanceSidebar/TokenBalances/index.tsx index 93bd4b8a..a8920dbd 100644 --- a/common/components/BalanceSidebar/TokenBalances/index.tsx +++ b/common/components/BalanceSidebar/TokenBalances/index.tsx @@ -25,25 +25,24 @@ export default class TokenBalances extends React.Component { public render() { const { tokens } = this.props; const shownTokens = tokens.filter( - token => !token.balance.eq(0) || token.custom || this.state.showAllTokens + token => !token.balance.eqn(0) || token.custom || this.state.showAllTokens ); return (
-
- {translate('sidebar_TokenBal')} -
+
{translate('sidebar_TokenBal')}
- {shownTokens.map(token => + {shownTokens.map(token => ( - )} + ))}
@@ -58,16 +57,15 @@ export default class TokenBalances extends React.Component { className="btn btn-default btn-xs" onClick={this.toggleShowCustomTokenForm} > - - {translate('SEND_custom')} - + {translate('SEND_custom')} - {this.state.showCustomTokenForm && + {this.state.showCustomTokenForm && (
-
} + + )}
); } diff --git a/common/components/BalanceSidebar/index.tsx b/common/components/BalanceSidebar/index.tsx index 41dcf5a9..5098fedf 100644 --- a/common/components/BalanceSidebar/index.tsx +++ b/common/components/BalanceSidebar/index.tsx @@ -7,7 +7,7 @@ import { import { showNotification, TShowNotification } from 'actions/notifications'; import { fetchCCRates as dFetchCCRates, TFetchCCRates } from 'actions/rates'; import { NetworkConfig } from 'config/data'; -import { Ether } from 'libs/units'; +import { Wei } from 'libs/units'; import { IWallet } from 'libs/wallet/IWallet'; import React from 'react'; import { connect } from 'react-redux'; @@ -27,7 +27,7 @@ import OfflineToggle from './OfflineToggle'; interface Props { wallet: IWallet; - balance: Ether; + balance: Wei; network: NetworkConfig; tokenBalances: TokenBalance[]; rates: State['rates']; diff --git a/common/components/Header/index.tsx b/common/components/Header/index.tsx index 5aa6c2c2..7811a496 100644 --- a/common/components/Header/index.tsx +++ b/common/components/Header/index.tsx @@ -91,9 +91,9 @@ export default class Header extends Component {
{
{ ); }; - private renderWalletRow(wallet) { + private renderWalletRow(wallet: DeterministicWalletData) { const { desiredToken, network } = this.props; const { selectedAddress } = this.state; // Get renderable values, but keep 'em short - const value = wallet.value ? wallet.value.toEther().toPrecision(4) : ''; - const tokenValue = wallet.tokenValues[desiredToken] - ? wallet.tokenValues[desiredToken].toPrecision(4) - : ''; + const token = wallet.tokenValues[desiredToken]; return ( { {wallet.address} - {value} {network.unit} + - {tokenValue} {desiredToken} + {token ? ( + + ) : ( + '???' + )} { } } -function mapStateToProps(state) { +function mapStateToProps(state: AppState) { return { wallets: state.deterministicWallets.wallets, desiredToken: state.deterministicWallets.desiredToken, diff --git a/common/components/WalletDecrypt/Keystore.tsx b/common/components/WalletDecrypt/Keystore.tsx index a18e4d57..efc5210c 100644 --- a/common/components/WalletDecrypt/Keystore.tsx +++ b/common/components/WalletDecrypt/Keystore.tsx @@ -54,9 +54,9 @@ export default class KeystoreDecrypt extends Component {

{translate('ADD_Label_3')}

0 - ? 'is-valid' - : 'is-invalid'}`} + className={`form-control ${ + password.length > 0 ? 'is-valid' : 'is-invalid' + }`} value={password} onChange={this.onPasswordChange} onKeyDown={this.onKeyDown} diff --git a/common/components/renderCbs/UnitConverter.tsx b/common/components/renderCbs/UnitConverter.tsx new file mode 100644 index 00000000..e02f3c15 --- /dev/null +++ b/common/components/renderCbs/UnitConverter.tsx @@ -0,0 +1,60 @@ +import { toTokenBase } from 'libs/units'; + +import React, { Component } from 'react'; + +interface IChildren { + onUserInput: UnitConverter['onUserInput']; + convertedUnit: string; +} +interface IFakeEvent { + currentTarget: { + value: string; + }; +} + +export interface Props { + decimal: number; + children({ onUserInput, convertedUnit }: IChildren): React.ReactElement; + onChange(baseUnit: IFakeEvent); +} + +interface State { + userInput: string; +} + +const initialState = { userInput: '' }; + +export class UnitConverter extends Component { + public state: State = initialState; + + public componentWillReceiveProps(nextProps: Props) { + const { userInput } = this.state; + + if (this.props.decimal !== nextProps.decimal) { + this.baseUnitCb(userInput, nextProps.decimal); + } + } + + public onUserInput = (e: React.FormEvent) => { + const { value } = e.currentTarget; + const { decimal } = this.props; + this.baseUnitCb(value, decimal); + this.setState({ userInput: value }); + }; + + public render() { + return this.props.children({ + onUserInput: this.onUserInput, + convertedUnit: this.state.userInput + }); + } + private baseUnitCb = (value: string, decimal: number) => { + const baseUnit = toTokenBase(value, decimal).toString(); + const fakeEvent = { + currentTarget: { + value: baseUnit + } + }; + this.props.onChange(fakeEvent); + }; +} diff --git a/common/components/renderCbs/index.ts b/common/components/renderCbs/index.ts new file mode 100644 index 00000000..1c44c03a --- /dev/null +++ b/common/components/renderCbs/index.ts @@ -0,0 +1 @@ +export * from './UnitConverter'; diff --git a/common/components/ui/UnitDisplay.tsx b/common/components/ui/UnitDisplay.tsx new file mode 100644 index 00000000..f4f56f48 --- /dev/null +++ b/common/components/ui/UnitDisplay.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { + fromTokenBase, + getDecimal, + UnitKey, + Wei, + TokenValue +} from 'libs/units'; +import { formatNumber as format } from 'utils/formatters'; + +interface Props { + /** + * @description base value of the token / ether, incase of waiting for API calls, we can return '???' + * @type {TokenValue | Wei} + * @memberof Props + */ + value?: TokenValue | Wei; + /** + * @description Symbol to display to the right of the value, such as 'ETH' + * @type {string} + * @memberof Props + */ + symbol?: string; + /** + * @description display the long balance, if false, trims it to 3 decimal places, if a number is specified then that number is the number of digits to be displayed. + * @type {boolean} + * @memberof Props + */ + displayShortBalance?: boolean | number; +} + +interface EthProps extends Props { + unit: UnitKey; +} +interface TokenProps extends Props { + decimal: number; +} + +const isEthereumUnit = (param: EthProps | TokenProps): param is EthProps => + !!(param as EthProps).unit; + +const UnitDisplay: React.SFC = params => { + const { value, symbol, displayShortBalance } = params; + + if (!value) { + return ???; + } + + const convertedValue = isEthereumUnit(params) + ? fromTokenBase(value, getDecimal(params.unit)) + : fromTokenBase(value, params.decimal); + + let formattedValue; + + if (displayShortBalance) { + const digits = + typeof displayShortBalance === 'number' && displayShortBalance; + formattedValue = digits + ? format(convertedValue, digits) + : format(convertedValue); + } else { + formattedValue = convertedValue; + } + + return ( + + {formattedValue} + {symbol ? ` ${symbol}` : ''} + + ); +}; + +export default UnitDisplay; diff --git a/common/components/ui/index.ts b/common/components/ui/index.ts index dd6af4cd..282c6d6c 100644 --- a/common/components/ui/index.ts +++ b/common/components/ui/index.ts @@ -6,3 +6,4 @@ export { default as Modal } from './Modal'; export { default as UnlockHeader } from './UnlockHeader'; export { default as QRCode } from './QRCode'; export { default as NewTabLink } from './NewTabLink'; +export { default as UnitDisplay } from './UnitDisplay'; diff --git a/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/index.tsx b/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/index.tsx index 902d08e0..b554882e 100644 --- a/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/index.tsx +++ b/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/index.tsx @@ -1,4 +1,5 @@ -import Big from 'bignumber.js'; +import BN from 'bn.js'; +import { Wei } from 'libs/units'; import React, { Component } from 'react'; import { generateCompleteTransaction as makeAndSignTx, @@ -12,7 +13,6 @@ import { } from 'containers/Tabs/Contracts/components/TxModal'; import { TxCompare, - Props as TCProps, TTxCompare } from 'containers/Tabs/Contracts/components/TxCompare'; import { withTx } from 'containers/Tabs/Contracts/components//withTx'; @@ -81,25 +81,13 @@ export const deployHOC = PassedComponent => { } private displayCompareTx = (): React.ReactElement => { - const { nonce, gasLimit, data, value, signedTx, to } = this.state; - const { gasPrice, chainId } = this.props; + const { signedTx, nonce } = this.state; if (!nonce || !signedTx) { throw Error('Can not display raw tx, nonce empty or no signed tx'); } - const props: TCProps = { - nonce, - gasPrice, - chainId, - data, - gasLimit, - to, - value, - signedTx - }; - - return ; + return ; }; private displayDeployModal = (): React.ReactElement => { @@ -143,7 +131,7 @@ export const deployHOC = PassedComponent => { props.wallet, props.nodeLib, props.gasPrice, - new Big(gasLimit), + Wei(gasLimit), props.chainId, transactionInput, true @@ -154,7 +142,7 @@ export const deployHOC = PassedComponent => { const address = await this.props.wallet.getAddressString(); const nonce = await this.props.nodeLib .getTransactionCount(address) - .then(n => new Big(n).toString()); + .then(n => new BN(n).toString()); return this.asyncSetState({ nonce, address }); }; } diff --git a/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/types.ts b/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/types.ts index 67e3ea4c..d0ca0bcc 100644 --- a/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/types.ts +++ b/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/types.ts @@ -1,4 +1,4 @@ -import { Wei, Ether } from 'libs/units'; +import { Wei } from 'libs/units'; import { IWallet } from 'libs/wallet/IWallet'; import { RPCNode } from 'libs/nodes'; import { NodeConfig, NetworkConfig } from 'config/data'; @@ -7,7 +7,7 @@ import { TShowNotification } from 'actions/notifications'; export interface Props { wallet: IWallet; - balance: Ether; + balance: Wei; node: NodeConfig; nodeLib: RPCNode; chainId: NetworkConfig['chainId']; diff --git a/common/containers/Tabs/Contracts/components/Interact/components/InteractExplorer/index.tsx b/common/containers/Tabs/Contracts/components/Interact/components/InteractExplorer/index.tsx index d42383d5..34810dc7 100644 --- a/common/containers/Tabs/Contracts/components/Interact/components/InteractExplorer/index.tsx +++ b/common/containers/Tabs/Contracts/components/Interact/components/InteractExplorer/index.tsx @@ -8,6 +8,8 @@ import WalletDecrypt from 'components/WalletDecrypt'; import { TShowNotification } from 'actions/notifications'; import classnames from 'classnames'; import { isValidGasPrice, isValidValue } from 'libs/validators'; +import { UnitConverter } from 'components/renderCbs'; +import { getDecimal } from 'libs/units'; export interface Props { contractFunctions: any; @@ -164,19 +166,26 @@ export default class InteractExplorer extends Component {