Skip to content

Commit

Permalink
GIX-1889: Fix blank modal disburse maturity (#3317)
Browse files Browse the repository at this point in the history
# Motivation

There was a bug when NeuronSelectPercentage was used for maturity above
1000.

That's because now we started calculating the maturity of the
percentage. Yet, the maturity was passed as a string and then converted
to a number. If the number was larger than 1000, then the string
couldn't be converted successfully to a number.

The solution was to expect a number instead of a string.

# Changes

* Change `formttedMaturity` in NeuronSelectPercentage for
`availableMaturityE8s`. Change accordingly.
* Change where the NeuronSelectPercentage was used to pass the maturity
as e8s instead of as a string.
* Remove unnecessary util `maturityPercentageToE8s`. Now it's a simple
multiplication and division. No need to use an util for that. There is
no logic to reuse.

# Tests

* I added a test that fails in `main` but succeeds in this PR.

# Todos

- [ ] Add entry to changelog (if necessary).
  • Loading branch information
lmuntaner authored Sep 14, 2023
1 parent 5c8b6fb commit 253a302
Show file tree
Hide file tree
Showing 10 changed files with 38 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,16 @@
import { InputRange, KeyValuePair } from "@dfinity/gix-components";
import { createEventDispatcher } from "svelte";
import TestIdWrapper from "$lib/components/common/TestIdWrapper.svelte";
import { maturityPercentageToE8s } from "$lib/utils/neuron.utils";
import { formatMaturity } from "$lib/utils/neuron.utils";
import { replacePlaceholders } from "$lib/utils/i18n.utils";
export let formattedMaturity: string;
export let availableMaturityE8s: bigint;
export let percentage: number;
export let buttonText: string;
export let disabled = false;
let maturityE8s: bigint;
$: maturityE8s = maturityPercentageToE8s({
percentage,
total: Number(formattedMaturity),
});
let selectedMaturityE8s: bigint;
$: selectedMaturityE8s = (availableMaturityE8s * BigInt(percentage)) / 100n;
const dispatcher = createEventDispatcher();
const selectPercentage = () => dispatcher("nnsSelectPercentage");
Expand All @@ -29,7 +25,7 @@
>{$i18n.neuron_detail.available_maturity}</span
>
<span class="value" slot="value" data-tid="available-maturity"
>{formattedMaturity}</span
>{formatMaturity(availableMaturityE8s)}</span
>
</KeyValuePair>

Expand All @@ -47,7 +43,7 @@
<h5>
<span class="description" data-tid="amount-maturity"
>{replacePlaceholders($i18n.neuron_detail.amount_maturity, {
$amount: formatMaturity(maturityE8s),
$amount: formatMaturity(selectedMaturityE8s),
})}</span
>
<span data-tid="percentage-to-disburse"
Expand Down
24 changes: 10 additions & 14 deletions frontend/src/lib/modals/neurons/DisburseMaturityModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@
KeyValuePair,
} from "@dfinity/gix-components";
import { formatToken } from "$lib/utils/token.utils";
import {
formatMaturity,
maturityPercentageToE8s,
} from "$lib/utils/neuron.utils";
import { formatMaturity } from "$lib/utils/neuron.utils";
export let formattedMaturity: string;
export let availableMaturityE8s: bigint;
export let tokenSymbol: string;
const steps: WizardSteps = [
Expand All @@ -44,19 +41,18 @@
const goToConfirm = () => modal.next();
let maturityToDisburse = 0n;
$: maturityToDisburse = maturityPercentageToE8s({
total: Number(formattedMaturity),
percentage: percentageToDisburse,
});
let maturityToDisburseE8s: bigint;
$: maturityToDisburseE8s =
(availableMaturityE8s * BigInt(percentageToDisburse)) / 100n;
// +/- 5%
let predictedMinimumTokens: string;
$: predictedMinimumTokens = formatToken({
value: BigInt(Number(maturityToDisburse) * 0.95),
value: BigInt(Math.round(Number(maturityToDisburseE8s) * 0.95)),
});
let predictedMaximumTokens: string;
$: predictedMaximumTokens = formatToken({
value: BigInt(Number(maturityToDisburse) * 1.05),
value: BigInt(Math.round(Number(maturityToDisburseE8s) * 1.05)),
});
</script>

Expand All @@ -73,7 +69,7 @@

{#if currentStep?.name === "SelectPercentage"}
<NeuronSelectPercentage
{formattedMaturity}
{availableMaturityE8s}
buttonText={$i18n.neuron_detail.disburse}
on:nnsSelectPercentage={goToConfirm}
on:nnsCancel={close}
Expand Down Expand Up @@ -126,7 +122,7 @@
>{$i18n.neuron_detail.disburse_maturity_confirmation_amount}</span
>
<span data-tid="confirm-amount" class="value" slot="value"
>{formatMaturity(maturityToDisburse)}
>{formatMaturity(maturityToDisburseE8s)}
</span>
</KeyValuePair>
<KeyValuePair>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</script>

<StakeMaturityModal
formattedMaturity={maturity}
availableMaturityE8s={neuron.fullNeuron?.maturityE8sEquivalent ?? 0n}
on:nnsStakeMaturity={stakeNeuronMaturity}
on:nnsClose
/>
3 changes: 1 addition & 2 deletions frontend/src/lib/modals/neurons/SpawnNeuronModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import { spawnNeuron } from "$lib/services/neurons.services";
import { toastsShow } from "$lib/stores/toasts.store";
import {
formattedMaturity,
isEnoughMaturityToSpawn,
isNeuronControlledByHardwareWallet,
} from "$lib/utils/neuron.utils";
Expand Down Expand Up @@ -98,7 +97,7 @@
>
{#if currentStep?.name === "SelectPercentage"}
<NeuronSelectPercentage
formattedMaturity={formattedMaturity(neuron)}
availableMaturityE8s={neuron.fullNeuron?.maturityE8sEquivalent ?? 0n}
buttonText={$i18n.neuron_detail.spawn}
on:nnsSelectPercentage={spawnNeuronFromMaturity}
on:nnsCancel={close}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/modals/neurons/StakeMaturityModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
type WizardStep,
} from "@dfinity/gix-components";
export let formattedMaturity: string;
export let availableMaturityE8s: bigint;
const steps: WizardSteps = [
{
Expand Down Expand Up @@ -45,7 +45,7 @@

{#if currentStep?.name === "SelectPercentage"}
<NeuronSelectPercentage
{formattedMaturity}
{availableMaturityE8s}
buttonText={$i18n.neuron_detail.stake}
on:nnsSelectPercentage={goToConfirm}
on:nnsCancel={close}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
</script>

<DisburseMaturityModal
formattedMaturity={maturity}
availableMaturityE8s={neuron.maturity_e8s_equivalent}
tokenSymbol={token?.symbol ?? ""}
on:nnsDisburseMaturity={disburseMaturity}
on:nnsClose
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
</script>

<StakeMaturityModal
formattedMaturity={maturity}
availableMaturityE8s={neuron.maturity_e8s_equivalent}
on:nnsStakeMaturity={stakeNeuronMaturity}
on:nnsClose
/>
15 changes: 1 addition & 14 deletions frontend/src/lib/utils/neuron.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import { nowInSeconds } from "./date.utils";
import { formatNumber } from "./format.utils";
import { getVotingBallot, getVotingPower } from "./proposals.utils";
import { toNnsVote } from "./sns-proposals.utils";
import { formatToken, numberToE8s } from "./token.utils";
import { formatToken } from "./token.utils";
import { isDefined } from "./utils";

export type StateInfo = {
Expand Down Expand Up @@ -954,16 +954,3 @@ export const maturityLastDistribution = ({

export const neuronDashboardUrl = ({ neuronId }: NeuronInfo): string =>
`https://dashboard.internetcomputer.org/neuron/${neuronId.toString()}`;

export const maturityPercentageToE8s = ({
total,
percentage,
}: {
total: number;
percentage: number;
}): bigint =>
numberToE8s(
// Use toFixed to avoid Token validation error "Number X has more than 8 decimals"
// due to `numberToE8s` validation of floating-point approximation issues of IEEE 754 (e.g. 0.1 + 0.2 = 0.30000000000000004)
Number(((percentage / 100) * total).toFixed(8))
);
19 changes: 16 additions & 3 deletions frontend/src/tests/lib/modals/sns/SnsDisburseMaturityModal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe("SnsDisburseMaturityModal", () => {
};

beforeEach(() => {
jest.clearAllMocks();
authStore.setForTesting(mockIdentity);
});

Expand Down Expand Up @@ -80,8 +81,8 @@ describe("SnsDisburseMaturityModal", () => {
expect(await po.getConfirmDestination()).toBe("Main");
});

it("should call disburse maturity api and reloadNeuron", async () => {
const po = await renderSnsDisburseMaturityModal();
const disburse = async (neuron: SnsNeuron) => {
const po = await renderSnsDisburseMaturityModal(neuron);
await po.setPercentage(50);
await po.clickNextButton();

Expand All @@ -93,11 +94,23 @@ describe("SnsDisburseMaturityModal", () => {

expect(disburseMaturity).toBeCalledTimes(1);
expect(disburseMaturity).toBeCalledWith({
neuronId: mockSnsNeuron.id,
neuronId: neuron.id,
rootCanisterId: mockPrincipal,
percentageToDisburse: 50,
identity: mockIdentity,
});
await waitFor(() => expect(reloadNeuron).toBeCalledTimes(1));
};

it("should call disburse maturity api and reloadNeuron", async () => {
await disburse(mockSnsNeuron);
});

it("should disburse maturity when maturity is larger than 1,000", async () => {
const neuron1100maturity = createMockSnsNeuron({
id: [1],
maturity: 110_000_000_000n,
});
await disburse(neuron1100maturity);
});
});
21 changes: 0 additions & 21 deletions frontend/src/tests/lib/utils/neuron.utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ import {
mapMergeableNeurons,
mapNeuronIds,
maturityLastDistribution,
maturityPercentageToE8s,
minNeuronSplittable,
neuronAge,
neuronCanBeSplit,
Expand Down Expand Up @@ -2422,24 +2421,4 @@ describe("neuron-utils", () => {
).toBe(false);
});
});

describe("maturityPercentageToE8s", () => {
it("calculates percents ", () => {
expect(
maturityPercentageToE8s({
total: 100,
percentage: 50,
})
).toEqual(5_000_000_000n);
});

it("handles more than 8 decimals results", () => {
expect(
maturityPercentageToE8s({
total: 1.00001,
percentage: 1,
})
).toEqual(1_000_010n);
});
});
});

0 comments on commit 253a302

Please sign in to comment.