Skip to content

Commit

Permalink
use HugeDecimal in StakingModal and PercentButton
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahSaso committed Sep 30, 2024
1 parent 7b7e922 commit e4262ee
Show file tree
Hide file tree
Showing 20 changed files with 314 additions and 350 deletions.
60 changes: 47 additions & 13 deletions packages/math/HugeDecimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,26 @@ export class HugeDecimal {
)
}

/**
* Returns a HugeDecimal whose value is the maximum of the arguments.
*
* @param n values
* @returns a HugeDecimal instance
*/
static max(...n: HugeDecimal.Value[]) {
return new HugeDecimal(BigNumber.max(...n.map(valueToBigNumber)))
}

/**
* Returns a HugeDecimal whose value is the minimum of the arguments.
*
* @param n values
* @returns a HugeDecimal instance
*/
static min(...n: HugeDecimal.Value[]) {
return new HugeDecimal(BigNumber.min(...n.map(valueToBigNumber)))
}

/**
* Returns a HugeDecimal instance with value 0.
*/
Expand Down Expand Up @@ -106,6 +126,16 @@ export class HugeDecimal {
return this.value.toString(10)
}

/**
* Returns a string in normal notation with exactly `decimals` decimal places.
*
* @param decimals decimals
* @returns a string
*/
toFixed(decimals: number) {
return this.value.toFixed(decimals, BigNumber.ROUND_DOWN)
}

valueOf() {
return this.value.valueOf()
}
Expand Down Expand Up @@ -173,16 +203,23 @@ export class HugeDecimal {
}

/**
* Returns a human-readable BigNumber instance with `decimals` decimal places.
* This is only meant to be used internally for formatting, since we don't
* want to encourage creating HugeDecimal instances with decimal places.
* Ideally, the underlying value is always in raw integer format.
* Returns a HugeDecimal whose value is the value of this HugeDecimal negated,
* i.e. multiplied by -1.
*/
negated() {
return new HugeDecimal(this.value.negated())
}

/**
* Returns a HugeDecimal instance with `decimals` decimal places.
*
* @param decimals the number of decimal places
* @returns human-readable BigNumber instance
* @returns HugeDecimal instance
*/
private toHumanReadable(decimals: number): HugeDecimal {
return new HugeDecimal(this.value.div(BigNumber(10).pow(decimals)))
toHumanReadable(decimals: number): HugeDecimal {
return new HugeDecimal(
this.value.div(BigNumber(10).pow(decimals)).toFixed(decimals)
)
}

/**
Expand Down Expand Up @@ -257,7 +294,7 @@ export class HugeDecimal {
)
: 0

const intStr = BigInt(int.toString()).toLocaleString(
const intStr = BigInt(int.toFixed(0)).toLocaleString(
undefined,
showFullAmount
? undefined
Expand Down Expand Up @@ -303,7 +340,7 @@ export class HugeDecimal {
*/
toCoin(denom: string): Coin {
return {
amount: this.toString(),
amount: this.toFixed(0),
denom,
}
}
Expand All @@ -326,9 +363,6 @@ export class HugeDecimal {
* @returns USD value
*/
toUsdValue(decimals: number, usdUnitPrice: BigNumber.Value) {
return this.value
.div(BigNumber(10).pow(decimals))
.times(usdUnitPrice)
.toNumber()
return this.toHumanReadable(decimals).times(usdUnitPrice).toNumber()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,7 @@ export const CreateRewardDistributionComponent: ActionComponent<

const minAmount = HugeDecimal.one.toHumanReadableNumber(decimals)

const selectedBalance = HugeDecimal.from(
selectedToken?.balance ?? 0
).toHumanReadableNumber(decimals)
const selectedBalance = HugeDecimal.from(selectedToken?.balance ?? 0)
const warning =
!isCreating ||
tokens.loading ||
Expand All @@ -100,10 +98,11 @@ export const CreateRewardDistributionComponent: ActionComponent<
? undefined
: !selectedToken
? t('error.unknownDenom', { denom: denomOrAddress })
: initialFunds && initialFunds > selectedBalance
: initialFunds &&
selectedBalance.toHumanReadable(decimals).lt(initialFunds)
? t('error.insufficientFundsWarning', {
amount: selectedBalance.toLocaleString(undefined, {
maximumFractionDigits: decimals,
amount: selectedBalance.toInternationalizedHumanReadableString({
decimals,
}),
tokenSymbol: selectedToken.token.symbol,
})
Expand Down Expand Up @@ -297,28 +296,29 @@ export const CreateRewardDistributionComponent: ActionComponent<
onClick={() =>
setValue(
(fieldNamePrefix + 'initialFunds') as 'initialFunds',
selectedBalance
selectedBalance.toHumanReadableNumber(decimals)
)
}
showFullAmount
symbol={selectedToken.token.symbol}
/>
</div>

{selectedBalance > 0 && (
{selectedBalance.isPositive() && (
<div className="grid grid-cols-5 gap-1">
{[10, 25, 50, 75, 100].map((percent) => (
<PercentButton
key={percent}
amount={initialFunds}
decimals={decimals}
label={`${percent}%`}
amount={HugeDecimal.fromHumanReadable(
initialFunds,
selectedToken.token.decimals
)}
loadingMax={{ loading: false, data: selectedBalance }}
percent={percent / 100}
percent={percent}
setAmount={(amount) =>
setValue(
(fieldNamePrefix + 'initialFunds') as 'initialFunds',
amount
amount.toHumanReadableNumber(selectedToken.token.decimals)
)
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,19 @@ export const FundRewardDistributionComponent: ActionComponent<
tokens.data.find((t) =>
tokensEqual(t.token, selectedDistribution.token)
)?.balance || 0
).toHumanReadableNumber(selectedDistribution.token.decimals)
: 0
)
: HugeDecimal.zero

const warning =
!isCreating || tokens.loading || tokens.updating || !selectedDistribution
? undefined
: amount && amount > selectedBalance
: amount &&
selectedBalance
.toHumanReadable(selectedDistribution.token.decimals)
.lt(amount)
? t('error.insufficientFundsWarning', {
amount: selectedBalance.toLocaleString(undefined, {
maximumFractionDigits: selectedDistribution.token.decimals,
amount: selectedBalance.toInternationalizedHumanReadableString({
decimals: selectedDistribution.token.decimals,
}),
tokenSymbol: selectedDistribution.token.symbol,
})
Expand Down Expand Up @@ -185,28 +188,33 @@ export const FundRewardDistributionComponent: ActionComponent<
onClick={() =>
setValue(
(fieldNamePrefix + 'amount') as 'amount',
selectedBalance
selectedBalance.toHumanReadableNumber(
selectedDistribution.token.decimals
)
)
}
showFullAmount
symbol={selectedDistribution.token.symbol}
/>
</div>

{selectedBalance > 0 && (
{selectedBalance.isPositive() && (
<div className="grid grid-cols-5 gap-1">
{[10, 25, 50, 75, 100].map((percent) => (
<PercentButton
key={percent}
amount={amount}
decimals={selectedDistribution.token.decimals}
label={`${percent}%`}
amount={HugeDecimal.fromHumanReadable(
amount,
selectedDistribution.token.decimals
)}
loadingMax={{ loading: false, data: selectedBalance }}
percent={percent / 100}
percent={percent}
setAmount={(amount) =>
setValue(
(fieldNamePrefix + 'amount') as 'amount',
amount
amount.toHumanReadableNumber(
selectedDistribution.token.decimals
)
)
}
/>
Expand Down
43 changes: 23 additions & 20 deletions packages/stateful/actions/core/actions/Spend/Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export type SpendOptions = {
// If this is an IBC transfer, this is the path of chains.
ibcPath: LoadingDataWithError<string[]>
// If this is an IBC transfer, show the expected receive amount.
ibcAmountOut: LoadingDataWithError<number | undefined>
ibcAmountOut: LoadingDataWithError<HugeDecimal | undefined>
// If this is an IBC transfer and a multi-TX route exists that unwinds the
// tokens correctly but doesn't use PFM, this is the better path.
betterNonPfmIbcPath: LoadingData<string[] | undefined>
Expand Down Expand Up @@ -268,14 +268,13 @@ export const SpendComponent: ActionComponent<SpendOptions> = ({
token.denomOrAddress === spendDenom &&
(token.type === TokenType.Cw20) === isCw20
)
const balance = HugeDecimal.from(
selectedToken?.balance ?? 0
).toHumanReadableNumber(selectedToken?.token.decimals ?? 0)

const decimals = loadedCustomToken
? token.data.decimals
: selectedToken?.token.decimals || 0

const balance = HugeDecimal.from(selectedToken?.balance ?? 0)

// A warning if the denom was not found in the treasury or the amount is too
// high. We don't want to make this an error because often people want to
// spend funds that a previous action makes available, so just show a warning.
Expand All @@ -285,10 +284,10 @@ export const SpendComponent: ActionComponent<SpendOptions> = ({
? undefined
: !selectedToken
? t('error.unknownDenom', { denom: spendDenom })
: spendAmount > balance
: balance.toHumanReadable(decimals).lt(spendAmount)
? t('error.insufficientFundsWarning', {
amount: balance.toLocaleString(undefined, {
maximumFractionDigits: decimals,
amount: balance.toInternationalizedHumanReadableString({
decimals,
}),
tokenSymbol: symbol,
})
Expand Down Expand Up @@ -479,27 +478,31 @@ export const SpendComponent: ActionComponent<SpendOptions> = ({
decimals={selectedToken.token.decimals}
iconUrl={selectedToken.token.imageUrl}
onClick={() =>
setValue((fieldNamePrefix + 'amount') as 'amount', balance)
setValue(
(fieldNamePrefix + 'amount') as 'amount',
balance.toHumanReadableNumber(decimals)
)
}
showFullAmount
symbol={selectedToken.token.symbol}
/>
</div>

{balance > 0 && (
{balance.isPositive() && (
<div className="grid grid-cols-5 gap-1">
{[10, 25, 50, 75, 100].map((percent) => (
<PercentButton
key={percent}
amount={spendAmount}
decimals={selectedToken.token.decimals}
label={`${percent}%`}
amount={HugeDecimal.fromHumanReadable(
spendAmount,
decimals
)}
loadingMax={{ loading: false, data: balance }}
percent={percent / 100}
percent={percent}
setAmount={(amount) =>
setValue(
(fieldNamePrefix + 'amount') as 'amount',
amount
amount.toHumanReadableNumber(decimals)
)
}
/>
Expand Down Expand Up @@ -667,11 +670,11 @@ export const SpendComponent: ActionComponent<SpendOptions> = ({
fee: neutronTransferFee.data
.map(({ token, balance }) =>
t('format.token', {
amount: HugeDecimal.from(balance)
.toHumanReadableNumber(token.decimals)
.toLocaleString(undefined, {
maximumFractionDigits: token.decimals,
}),
amount: HugeDecimal.from(
balance
).toInternationalizedHumanReadableString({
decimals: token.decimals,
}),
symbol: token.symbol,
})
)
Expand Down Expand Up @@ -856,7 +859,7 @@ export const SpendComponent: ActionComponent<SpendOptions> = ({
!ibcAmountOut.loading &&
!ibcAmountOut.errored &&
ibcAmountOut.data &&
ibcAmountOut.data !== spendAmount && (
!ibcAmountOut.data.eq(spendAmount) && (
<div className="flex flex-col gap-2 mt-1">
<InputLabel name={t('form.amountReceived')} />

Expand Down
6 changes: 0 additions & 6 deletions packages/stateful/actions/core/actions/Spend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ guide](https://github.com/DA0-DA0/dao-dao-ui/wiki/Bulk-importing-actions).
}
```

You must set `decimals` correctly for the specified `amount` of funds. The final
message gets generated with `amount * 10^(decimals)` microunits of the specified
`denom`. For example: `amount = 5`, `decimals = 6`, and `denom = "untrn"` =>
`5000000untrn` or `5 * 10^6 untrn`, where `untrn` is the microdenom/true
denomination of `NTRN`.

If used in a DAO, `fromChainId` and `from` determine which account has the
tokens being sent, which can be the native chain or any supported Polytone or
ICA chain. `toChainId` is unrelated, and it determines if the tokens are sent to
Expand Down
Loading

0 comments on commit e4262ee

Please sign in to comment.