fix: withdraw/deposit converts from BZZ (10^16 base units) (#66)

* refactor: added toBZZbaseUnit function

* feat: added utility for attesting value is BZZ convertible to base units

* fix: conversion from 15 to 16 decimal places, added unsafe versions

* refactor: withdraw modal uses the safe conversion from BZZ

* refactor: added BigNumber and Token class to handle BZZ digits correctly

* refactor: extract deposit and withdraw functionality into single modal

* test: added tests for Token

* chore: removed unused component

* chore: addressed PR review, token decimal is now integer 0-18

* chore: added comment to clarify the value restriction on token amount
This commit is contained in:
Vojtech Simetka
2021-04-13 12:26:29 +02:00
committed by GitHub
parent 343e388498
commit 825772cf73
19 changed files with 411 additions and 238 deletions
-80
View File
@@ -1,80 +0,0 @@
import React, { ReactElement } from 'react'
import Button from '@material-ui/core/Button'
import Input from '@material-ui/core/Input'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogContentText from '@material-ui/core/DialogContentText'
import DialogTitle from '@material-ui/core/DialogTitle'
import { Snackbar } from '@material-ui/core'
import { beeDebugApi } from '../services/bee'
export default function DepositModal(): ReactElement {
const [open, setOpen] = React.useState(false)
const [amount, setAmount] = React.useState(BigInt(0))
const [showToast, setToastVisibility] = React.useState(false)
const [toastContent, setToastContent] = React.useState('')
const handleClickOpen = () => {
setOpen(true)
}
const handleClose = () => {
setOpen(false)
}
const handleWithdraw = () => {
if (amount > 0) {
beeDebugApi.chequebook
.deposit(amount)
.then(res => {
setOpen(false)
handleToast(`Successful Deposit. Transaction ${res.transactionHash}`)
})
.catch(() => {
handleToast('Error with Deposit')
})
} else {
handleToast('Must be amount of greater than 0')
}
}
const handleToast = (text: string) => {
setToastContent(text)
setToastVisibility(true)
setTimeout(() => setToastVisibility(false), 7000)
}
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen} style={{ marginLeft: '7px' }}>
Deposit
</Button>
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Deposit Funds</DialogTitle>
<DialogContent>
<DialogContentText>Specify the amount you would like to deposit to your node.</DialogContentText>
<Input
autoFocus
margin="dense"
id="name"
type="number"
placeholder="Amount"
fullWidth
onChange={e => setAmount(BigInt(e.target.value))}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleWithdraw} color="primary">
Deposit
</Button>
</DialogActions>
</Dialog>
</div>
)
}
-46
View File
@@ -1,46 +0,0 @@
import React from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Paper, InputBase, IconButton } from '@material-ui/core'
import { Search } from '@material-ui/icons'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
padding: '2px 4px',
display: 'flex',
alignItems: 'center',
width: 400,
},
input: {
marginLeft: theme.spacing(1),
flex: 1,
},
iconButton: {
padding: 10,
},
divider: {
height: 28,
margin: 4,
},
}),
)
export default function SearchBar() {
const classes = useStyles()
return (
<div>
<Paper component="form" className={classes.root}>
<InputBase
className={classes.input}
placeholder="Enter hash e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
inputProps={{ 'aria-label': 'search google maps' }}
/>
<IconButton type="submit" className={classes.iconButton} aria-label="search">
<Search />
</IconButton>
</Paper>
</div>
)
}
+118
View File
@@ -0,0 +1,118 @@
import { ReactElement, useState } from 'react'
import Button from '@material-ui/core/Button'
import Input from '@material-ui/core/Input'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogContentText from '@material-ui/core/DialogContentText'
import DialogTitle from '@material-ui/core/DialogTitle'
import { FormHelperText, Snackbar } from '@material-ui/core'
import { Token } from '../models/Token'
import type { BigNumber } from 'bignumber.js'
interface Props {
successMessage: string
errorMessage: string
dialogMessage: string
label: string
max?: BigNumber
min?: BigNumber
action: (amount: bigint) => Promise<{ transactionHash: string }>
}
export default function WithdrawModal({
successMessage,
errorMessage,
dialogMessage,
min,
max,
label,
action,
}: Props): ReactElement {
const [open, setOpen] = useState(false)
const [amount, setAmount] = useState('')
const [amountToken, setAmountToken] = useState<Token | null>(null)
const [amountError, setAmountError] = useState<Error | null>(null)
const [showToast, setToastVisibility] = useState(false)
const [toastContent, setToastContent] = useState('')
const handleClickOpen = () => {
setOpen(true)
}
const handleClose = () => {
setOpen(false)
}
const handleAction = async () => {
if (amountToken === null) return
try {
const { transactionHash } = await action(amountToken.toBigInt)
setOpen(false)
handleToast(`${successMessage} Transaction ${transactionHash}`)
} catch (e) {
handleToast(`${errorMessage} Error: ${e.message}`)
}
}
const handleToast = (text: string) => {
setToastContent(text)
setToastVisibility(true)
setTimeout(() => setToastVisibility(false), 7000)
}
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
const value = e.target.value
setAmount(value)
setAmountError(null)
try {
const t = Token.fromDecimal(value)
setAmountToken(t)
if (min && t.toDecimal.isLessThan(min)) setAmountError(new Error(`Needs to be more than ${min}`))
if (max && t.toDecimal.isGreaterThan(max)) setAmountError(new Error(`Needs to be less than ${max}`))
} catch (e) {
setAmountError(e)
}
}
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
{label}
</Button>
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">{label}</DialogTitle>
<DialogContent>
<DialogContentText>{dialogMessage}</DialogContentText>
<Input
autoFocus
margin="dense"
id="name"
type="text"
placeholder="Amount"
fullWidth
value={amount}
onChange={handleChange}
/>
{amountError && (
<FormHelperText error>
Please provide valid BZZ amount (max 16 decimals). Error: {amountError.message}
</FormHelperText>
)}
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleAction} color="primary">
{label}
</Button>
</DialogActions>
</Dialog>
</div>
)
}
-81
View File
@@ -1,81 +0,0 @@
import { ReactElement, useState } from 'react'
import Button from '@material-ui/core/Button'
import Input from '@material-ui/core/Input'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogContentText from '@material-ui/core/DialogContentText'
import DialogTitle from '@material-ui/core/DialogTitle'
import { Snackbar } from '@material-ui/core'
import { beeDebugApi } from '../services/bee'
export default function WithdrawlModal(): ReactElement {
const [open, setOpen] = useState(false)
const [amount, setAmount] = useState(BigInt(0))
const [showToast, setToastVisibility] = useState(false)
const [toastContent, setToastContent] = useState('')
const handleClickOpen = () => {
setOpen(true)
}
const handleClose = () => {
setOpen(false)
}
const handleWithdraw = () => {
if (amount > 0) {
beeDebugApi.chequebook
.withdraw(amount)
.then(res => {
setOpen(false)
handleToast(`Successful withdrawl. Transaction ${res.transactionHash}`)
})
.catch(() => {
// FIXME: should probably detail the error
handleToast('Error with withdrawing')
})
} else {
handleToast('Must be amount of greater than 0')
}
}
const handleToast = (text: string) => {
setToastContent(text)
setToastVisibility(true)
setTimeout(() => setToastVisibility(false), 7000)
}
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Withdraw
</Button>
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Withdraw Funds</DialogTitle>
<DialogContent>
<DialogContentText>Specify the amount you would like to withdraw from your node.</DialogContentText>
<Input
autoFocus
margin="dense"
id="name"
type="number"
placeholder="Amount"
fullWidth
onChange={e => setAmount(BigInt(e.target.value))}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleWithdraw} color="primary">
Withdraw
</Button>
</DialogActions>
</Dialog>
</div>
)
}
+18
View File
@@ -0,0 +1,18 @@
import type { ReactElement } from 'react'
import { beeDebugApi } from '../services/bee'
import WDModal from '../components/WDModal'
import { BigNumber } from 'bignumber.js'
export default function DepositModal(): ReactElement {
return (
<WDModal
successMessage="Successful deposit."
errorMessage="Error with depositing"
dialogMessage="Specify the amount of BZZ you would like to withdraw from your node."
label="Deposit"
min={new BigNumber(0)}
action={beeDebugApi.chequebook.deposit}
/>
)
}
+18
View File
@@ -0,0 +1,18 @@
import type { ReactElement } from 'react'
import { beeDebugApi } from '../services/bee'
import WDModal from '../components/WDModal'
import { BigNumber } from 'bignumber.js'
export default function WithdrawModal(): ReactElement {
return (
<WDModal
successMessage="Successful withdrawl."
errorMessage="Error with withdrawing."
dialogMessage="Specify the amount of BZZ you would like to withdraw from your node."
label="Withdraw"
min={new BigNumber(0)}
action={beeDebugApi.chequebook.withdraw}
/>
)
}
+39
View File
@@ -0,0 +1,39 @@
import BigNumber from 'bignumber.js'
import { Token } from './Token'
describe('models/Token', () => {
describe('Token.fromDecimal', () => {
const values = [
{ bzz: '0', baseUnits: '0' },
{ bzz: '0.1', baseUnits: BigInt('1000000000000000') },
{ bzz: '9.9', baseUnits: BigInt('99000000000000000') },
]
// Test with default 16 decimal places
values.forEach(({ bzz, baseUnits }) => {
test(`converting ${bzz} => ${baseUnits}`, () => {
expect(Token.fromDecimal(bzz).toBigNumber.eq(baseUnits.toString())).toBe(true)
})
})
// Test with 0 decimal places
values.forEach(({ baseUnits }) => {
test(`converting ${baseUnits} => ${baseUnits} with 0 decimals`, () => {
expect(Token.fromDecimal(baseUnits, 0).toBigNumber.eq(baseUnits.toString())).toBe(true)
})
})
})
describe('new Token', () => {
const cs = ['0', '1234567890', '99000000000000000']
const correctValues = [...cs, ...cs.map(BigInt), ...cs.map(v => new BigNumber(v))]
correctValues.forEach(v => {
test(`New Token ${v} of type ${typeof v}`, () => {
const t = new Token(v)
expect(t.toBigNumber.eq(v.toString())).toBe(true)
})
})
})
})
+56
View File
@@ -0,0 +1,56 @@
import { BigNumber } from 'bignumber.js'
import { isInteger, makeBigNumber } from '../utils'
const POSSIBLE_DECIMALS = [18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
type digits = typeof POSSIBLE_DECIMALS[number]
const BZZ_DECIMALS = 16
export class Token {
private amount: BigNumber // Represented in the base units, so it is always an integer value
private readonly decimals: digits
constructor(amount: BigNumber | string | bigint, decimals: digits = BZZ_DECIMALS) {
const a = makeBigNumber(amount)
if (!isInteger(a) || !POSSIBLE_DECIMALS.includes(decimals)) throw new TypeError('Not a valid token values')
this.amount = a
this.decimals = decimals
}
/**
* Construct new Token from a digit representation
*
* @param amount Amount of a token in the digits (1 token = 10^decimals)
* @param decimals Number of decimals for the token (must be integer)
*
* @throws {TypeError} If the decimals is not an integer or the amount after conversion is not an integer
*
* @returns new Token
*/
static fromDecimal(amount: BigNumber | string | bigint, decimals: digits = BZZ_DECIMALS): Token | never {
const a = makeBigNumber(amount)
// No need to do any validation here, it is done when the new token is created
const t = a.multipliedBy(new BigNumber(10).pow(decimals))
return new Token(t, decimals)
}
get toBigInt(): bigint {
return BigInt(this.amount.toFixed(0))
}
get toString(): string {
return this.amount.toFixed(0)
}
get toBigNumber(): BigNumber {
return new BigNumber(this.amount)
}
get toDecimal(): BigNumber {
return this.amount.dividedBy(new BigNumber(10).pow(this.decimals))
}
}
+12 -12
View File
@@ -3,11 +3,11 @@ import { ReactElement } from 'react'
import { createStyles, makeStyles } from '@material-ui/core/styles'
import { Card, CardContent, Typography, Grid } from '@material-ui/core/'
import { Skeleton } from '@material-ui/lab'
import WithdrawlModal from '../../components/WithdrawlModal'
import DepositModal from '../../components/DepositModal'
import WithdrawModal from '../../containers/WithdrawModal'
import DepositModal from '../../containers/DepositModal'
import CashoutModal from '../../components/CashoutModal'
import { ConvertBalanceToBZZ } from '../../utils/common'
import { fromBZZbaseUnit } from '../../utils'
import type { AllSettlements, ChequebookAddressResponse } from '@ethersphere/bee-js'
@@ -55,7 +55,7 @@ function AccountCard(props: Props): ReactElement {
<div style={{ justifyContent: 'space-between', display: 'flex' }}>
<h2 style={{ marginTop: '0px' }}>Accounting</h2>
<div style={{ display: 'flex' }}>
<WithdrawlModal />
<WithdrawModal />
<DepositModal />
<CashoutModal />
</div>
@@ -68,28 +68,28 @@ function AccountCard(props: Props): ReactElement {
<Grid container spacing={5}>
<Grid item>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Total Balance
Total Balance (BZZ)
</Typography>
<Typography component="p" variant="h5">
{ConvertBalanceToBZZ(props.chequebookBalance.totalBalance)}
{fromBZZbaseUnit(props.chequebookBalance.totalBalance)}
</Typography>
</Grid>
<Grid item>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Available Balance
Available Balance (BZZ)
</Typography>
<Typography component="p" variant="h5">
{ConvertBalanceToBZZ(props.chequebookBalance.availableBalance)}
{fromBZZbaseUnit(props.chequebookBalance.availableBalance)}
</Typography>
</Grid>
<Grid item>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Total Sent / Received
Total Sent / Received (BZZ)
</Typography>
<Typography component="div" variant="h5">
<span style={{ marginRight: '7px' }}>
{ConvertBalanceToBZZ(props.settlements?.totalsent || 0)} /{' '}
{ConvertBalanceToBZZ(props.settlements?.totalreceived || 0)}
{fromBZZbaseUnit(props.settlements?.totalsent || 0)} /{' '}
{fromBZZbaseUnit(props.settlements?.totalreceived || 0)}
</span>
<span
style={{
@@ -100,7 +100,7 @@ function AccountCard(props: Props): ReactElement {
}}
>
(
{ConvertBalanceToBZZ(
{fromBZZbaseUnit(
(props.settlements && props.settlements?.totalsent - props.settlements?.totalreceived) || 0,
)}
)
+3 -3
View File
@@ -12,7 +12,7 @@ import {
CircularProgress,
} from '@material-ui/core'
import { ConvertBalanceToBZZ } from '../../utils/common'
import { fromBZZbaseUnit } from '../../utils'
const useStyles = makeStyles({
table: {
@@ -59,12 +59,12 @@ function BalancesTable(props: Props): ReactElement {
<TableCell>{peerBalance.peer}</TableCell>
<TableCell
style={{
color: ConvertBalanceToBZZ(peerBalance.balance) > 0 ? '#32c48d' : '#c9201f',
color: fromBZZbaseUnit(peerBalance.balance) > 0 ? '#32c48d' : '#c9201f',
textAlign: 'right',
fontFamily: 'monospace, monospace',
}}
>
{ConvertBalanceToBZZ(peerBalance.balance).toFixed(7).toLocaleString()}
{fromBZZbaseUnit(peerBalance.balance).toFixed(7).toLocaleString()}
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
+4 -4
View File
@@ -1,4 +1,4 @@
import React, { ReactElement } from 'react'
import type { ReactElement } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import {
Table,
@@ -12,7 +12,7 @@ import {
CircularProgress,
} from '@material-ui/core'
import { ConvertBalanceToBZZ } from '../../utils/common'
import { fromBZZbaseUnit } from '../../utils'
import EthereumAddress from '../../components/EthereumAddress'
import ClipboardCopy from '../../components/ClipboardCopy'
import PeerDetailDrawer from './PeerDetailDrawer'
@@ -80,7 +80,7 @@ function ChequebookTable(props: Props): ReactElement {
<p style={{ marginBottom: '0px', fontFamily: 'monospace, monospace', display: 'flex' }}>
<span style={{ whiteSpace: 'nowrap', marginRight: '12px', paddingTop: '3px' }}>
{peerCheque.lastreceived?.payout
? `${ConvertBalanceToBZZ(peerCheque.lastreceived?.payout).toFixed(7).toLocaleString()} from`
? `${fromBZZbaseUnit(peerCheque.lastreceived?.payout).toFixed(7).toLocaleString()} from`
: '-'}
</span>
{peerCheque.lastreceived ? (
@@ -97,7 +97,7 @@ function ChequebookTable(props: Props): ReactElement {
<p style={{ marginBottom: '0px', fontFamily: 'monospace, monospace', display: 'flex' }}>
<span style={{ whiteSpace: 'nowrap', marginRight: '12px', paddingTop: '3px' }}>
{peerCheque.lastsent?.payout
? `${ConvertBalanceToBZZ(peerCheque.lastsent?.payout).toFixed(7).toLocaleString()} to`
? `${fromBZZbaseUnit(peerCheque.lastsent?.payout).toFixed(7).toLocaleString()} to`
: '-'}
</span>
{peerCheque.lastsent ? (
+5 -5
View File
@@ -3,7 +3,7 @@ import { Paper, Container, Drawer, Button, Typography, CircularProgress, Grid }
import ClipboardCopy from '../../components/ClipboardCopy'
import { beeDebugApi } from '../../services/bee'
import EthereumAddress from '../../components/EthereumAddress'
import { ConvertBalanceToBZZ } from '../../utils/common'
import { fromBZZbaseUnit } from '../../utils'
import { LastCashoutActionResponse, LastChequesForPeerResponse } from '@ethersphere/bee-js'
function truncStringPortion(str: string, firstCharCount = 10, endCharCount = 10) {
@@ -87,7 +87,7 @@ export default function Index(props: Props): ReactElement {
Payout:
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
{' '}
{peerCheque?.lastsent?.payout ? ConvertBalanceToBZZ(peerCheque?.lastsent?.payout) : '-'}
{peerCheque?.lastsent?.payout ? fromBZZbaseUnit(peerCheque?.lastsent?.payout) : '-'}
</span>
</p>
<p>
@@ -105,7 +105,7 @@ export default function Index(props: Props): ReactElement {
Payout:
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
{' '}
{peerCheque?.lastreceived?.payout ? ConvertBalanceToBZZ(peerCheque?.lastreceived?.payout) : '-'}
{peerCheque?.lastreceived?.payout ? fromBZZbaseUnit(peerCheque?.lastreceived?.payout) : '-'}
</span>
</p>
<p>
@@ -124,14 +124,14 @@ export default function Index(props: Props): ReactElement {
<p>
Cumulative Payout:
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
{peerCashout?.cumulativePayout ? ConvertBalanceToBZZ(peerCashout?.cumulativePayout) : '-'}
{peerCashout?.cumulativePayout ? fromBZZbaseUnit(peerCashout?.cumulativePayout) : '-'}
</span>
</p>
<p>
Last Payout:
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
{' '}
{peerCashout?.result.lastPayout ? ConvertBalanceToBZZ(peerCashout?.result.lastPayout) : '-'}
{peerCashout?.result.lastPayout ? fromBZZbaseUnit(peerCashout?.result.lastPayout) : '-'}
</span>
<span> {peerCashout?.result.bounced ? 'Bounced' : ''}</span>
</p>
+3 -3
View File
@@ -12,7 +12,7 @@ import {
CircularProgress,
} from '@material-ui/core'
import { ConvertBalanceToBZZ } from '../../utils/common'
import { fromBZZbaseUnit } from '../../utils'
import type { AllSettlements, Settlements } from '@ethersphere/bee-js'
@@ -51,10 +51,10 @@ function SettlementsTable(props: Props): ReactElement {
<TableRow key={item.peer}>
<TableCell>{item.peer}</TableCell>
<TableCell style={{ fontFamily: 'monospace, monospace' }}>
{item.received > 0 ? ConvertBalanceToBZZ(item.received).toFixed(7).toLocaleString() : item.received}
{item.received > 0 ? fromBZZbaseUnit(item.received).toFixed(7).toLocaleString() : item.received}
</TableCell>
<TableCell style={{ fontFamily: 'monospace, monospace' }}>
{item.sent > 0 ? ConvertBalanceToBZZ(item.sent).toFixed(7).toLocaleString() : item.sent}
{item.sent > 0 ? fromBZZbaseUnit(item.sent).toFixed(7).toLocaleString() : item.sent}
</TableCell>
</TableRow>
))}
@@ -1,6 +1,6 @@
import { Typography } from '@material-ui/core/'
import EthereumAddress from '../../../components/EthereumAddress'
import DepositModal from '../../../components/DepositModal'
import DepositModal from '../../../containers/DepositModal'
import CodeBlockTabs from '../../../components/CodeBlockTabs'
import type { ReactElement } from 'react'
-3
View File
@@ -1,3 +0,0 @@
export const ConvertBalanceToBZZ = (amount: number): number => {
return amount / 10 ** 16
}
+71
View File
@@ -0,0 +1,71 @@
import BigNumber from 'bignumber.js'
import { fromBZZbaseUnit, isInteger, makeBigNumber } from './index'
describe('utils', () => {
describe('fromBZZbaseUnit', () => {
const values = [
{ bzz: 0, baseUnits: 0 },
{ bzz: 0.1, baseUnits: 1e15 },
{ bzz: 0.9, baseUnits: 9e15 },
]
values.forEach(({ bzz, baseUnits }) => {
test(`converting ${bzz} => ${baseUnits}`, () => {
expect(fromBZZbaseUnit(baseUnits)).toBe(bzz)
})
})
})
describe('isInteger', () => {
const correctValues = [
BigInt(0),
BigInt(1),
BigInt(-1),
new BigNumber('1'),
new BigNumber('0'),
new BigNumber('-1'),
]
const wrongValues = ['1', new BigNumber('-0.1'), new BigNumber(NaN), new BigNumber(Infinity)]
correctValues.forEach(v => {
test(`testing ${v}`, () => {
expect(isInteger(v)).toBe(true)
})
})
wrongValues.forEach(v => {
test(`testing ${v}`, () => {
expect(isInteger(v)).toBe(false)
})
})
})
describe('makeBigNumber', () => {
const correctValues = [
BigInt(0),
BigInt(1),
BigInt(-1),
'1',
'0',
'-1',
'0.1',
new BigNumber('1'),
new BigNumber('0'),
new BigNumber('-1'),
]
const wrongValues = [new Function(), 0, 1]
correctValues.forEach(v => {
test(`testing ${v}`, () => {
expect(BigNumber.isBigNumber(makeBigNumber(v))).toBe(true)
})
})
wrongValues.forEach(v => {
test(`testing ${v}`, () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect(() => makeBigNumber((v as unknown) as any)).toThrow()
})
})
})
})
+48
View File
@@ -0,0 +1,48 @@
import { BigNumber } from 'bignumber.js'
/**
* @deprecated Should be removed in favour of Token class
*/
const BZZ_BASE_UNIT = 1e16
/**
* Convert from base units of BZZ token to BZZ
* @deprecated This should only be used for displaying values, it's unsafe and should be replaced with Token class
*
* @param amount Amount in base units of BZZ token
*
* @returns amount in BZZ
*/
export const fromBZZbaseUnit = (amount: number): number => {
return amount / BZZ_BASE_UNIT
}
/**
* Test if value is an integer
*
* @param value Value to be tested if it is an integer
*
* @returns True if the passed in value is integer
*/
export function isInteger(value: unknown): value is BigNumber | bigint {
return (BigNumber.isBigNumber(value) && value.isInteger()) || typeof value === 'bigint'
}
/**
*Convert value into a BigNumber if not already
*
* @param value Value to be converted
*
* @throws {TypeError} if the value is not convertible to a BigNumber
*
* @returns BigNumber - but it may still be NaN or Infinite
*/
export function makeBigNumber(value: BigNumber | bigint | string): BigNumber | never {
if (BigNumber.isBigNumber(value)) return value
if (typeof value === 'string') return new BigNumber(value)
if (typeof value === 'bigint') return new BigNumber(value.toString())
throw new TypeError('Not a BigNumber or BigNumber convertible value')
}