From c5114aa43c8ab6b6ee6e299b218b4735955a6158 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lloren=C3=A7=20Muntaner?=
Date: Mon, 18 Sep 2023 19:16:59 +0200
Subject: [PATCH] GIX-1890: Check tx fee to disable disburse maturity button
(#3329)
# Motivation
Check the fee to enable the disburse maturity button.
# Changes
* DisburseMaturityButton: Change prop to `disabledText`.
* Add prop feeE8s to SnsDisburseMaturityButton
* Add prop feeE8s to SnsAvailableMaturityItemAction.
* Add prop feeE8s to SnsNeuronMaturitySection.
* New sns neuron util hasEnoughMaturityToDisburse
* Rename hasEnoughMaturityToStakeOrDisburse to hasEnoughMaturityToStake
* Use new util `hasEnoughMaturityToDisburse` and new prop `disabledText`
in SnsDisburseMaturityButton.
* Change copy in "disburse_maturity_disabled_tooltip" i18n key.
# Tests
* Adapt tests to new props.
* Adapt tests to `disabledText` prop name.
* Change test case in SnsDisburseMaturityButton.spec to check agains a
maturity less than fee.
* New test case in SnsAvailableMaturityItemAction
# Todos
- [ ] Add entry to changelog (if necessary).
Not worth an entry. Covered by disburse maturity entry.
---
.../actions/DisburseMaturityButton.svelte | 11 +++--
.../SnsAvailableMaturityItemAction.svelte | 3 +-
.../SnsNeuronMaturitySection.svelte | 3 +-
.../actions/SnsDisburseMaturityButton.svelte | 18 ++++++--
.../actions/SnsStakeMaturityButton.svelte | 4 +-
frontend/src/lib/i18n/en.json | 2 +-
frontend/src/lib/pages/SnsNeuronDetail.svelte | 5 ++-
frontend/src/lib/utils/sns-neuron.utils.ts | 15 ++++++-
.../actions/DisburseMaturityButton.spec.ts | 10 ++---
.../SnsAvailableMaturityItemAction.spec.ts | 15 ++++++-
.../SnsNeuronMaturitySection.spec.ts | 9 ++--
.../actions/SnsDisburseMaturityButton.spec.ts | 36 ++++++++++-----
.../tests/lib/utils/sns-neuron.utils.spec.ts | 45 ++++++++++++++++---
.../DisburseMaturityButton.page-object.ts | 5 +++
14 files changed, 135 insertions(+), 46 deletions(-)
diff --git a/frontend/src/lib/components/neuron-detail/actions/DisburseMaturityButton.svelte b/frontend/src/lib/components/neuron-detail/actions/DisburseMaturityButton.svelte
index 65371890071..cb36b317b61 100644
--- a/frontend/src/lib/components/neuron-detail/actions/DisburseMaturityButton.svelte
+++ b/frontend/src/lib/components/neuron-detail/actions/DisburseMaturityButton.svelte
@@ -2,20 +2,19 @@
import { i18n } from "$lib/stores/i18n";
import Tooltip from "$lib/components/ui/Tooltip.svelte";
import TestIdWrapper from "$lib/components/common/TestIdWrapper.svelte";
+ import { isNullish } from "@dfinity/utils";
- export let enoughMaturity: boolean;
+ // If the button is disabled, this will be the tooltip text.
+ export let disabledText: string | undefined = undefined;
- {#if enoughMaturity}
+ {#if isNullish(disabledText)}
{:else}
-
+
diff --git a/frontend/src/lib/components/sns-neuron-detail/SnsAvailableMaturityItemAction.svelte b/frontend/src/lib/components/sns-neuron-detail/SnsAvailableMaturityItemAction.svelte
index 2c331c2c8a8..36a618cc958 100644
--- a/frontend/src/lib/components/sns-neuron-detail/SnsAvailableMaturityItemAction.svelte
+++ b/frontend/src/lib/components/sns-neuron-detail/SnsAvailableMaturityItemAction.svelte
@@ -14,6 +14,7 @@
import { ENABLE_DISBURSE_MATURITY } from "$lib/stores/feature-flags.store";
export let neuron: SnsNeuron;
+ export let feeE8s: bigint;
let allowedToStakeMaturity: boolean;
$: allowedToStakeMaturity = hasPermissionToStakeMaturity({
@@ -40,6 +41,6 @@
{/if}
{#if allowedToDisburseMaturity && $ENABLE_DISBURSE_MATURITY}
-
+
{/if}
diff --git a/frontend/src/lib/components/sns-neuron-detail/SnsNeuronMaturitySection.svelte b/frontend/src/lib/components/sns-neuron-detail/SnsNeuronMaturitySection.svelte
index 0667cc08edc..95d6f796fd9 100644
--- a/frontend/src/lib/components/sns-neuron-detail/SnsNeuronMaturitySection.svelte
+++ b/frontend/src/lib/components/sns-neuron-detail/SnsNeuronMaturitySection.svelte
@@ -8,6 +8,7 @@
import SnsViewActiveDisbursementsItemAction from "$lib/components/sns-neuron-detail/SnsViewActiveDisbursementsItemAction.svelte";
export let neuron: SnsNeuron;
+ export let feeE8s: bigint;
diff --git a/frontend/src/lib/components/sns-neuron-detail/actions/SnsDisburseMaturityButton.svelte b/frontend/src/lib/components/sns-neuron-detail/actions/SnsDisburseMaturityButton.svelte
index 975d8dfa62c..f7714fb75f1 100644
--- a/frontend/src/lib/components/sns-neuron-detail/actions/SnsDisburseMaturityButton.svelte
+++ b/frontend/src/lib/components/sns-neuron-detail/actions/SnsDisburseMaturityButton.svelte
@@ -1,15 +1,27 @@
-
+
diff --git a/frontend/src/lib/components/sns-neuron-detail/actions/SnsStakeMaturityButton.svelte b/frontend/src/lib/components/sns-neuron-detail/actions/SnsStakeMaturityButton.svelte
index ab5161c4ab2..ad8b1d8787f 100644
--- a/frontend/src/lib/components/sns-neuron-detail/actions/SnsStakeMaturityButton.svelte
+++ b/frontend/src/lib/components/sns-neuron-detail/actions/SnsStakeMaturityButton.svelte
@@ -1,13 +1,13 @@
diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json
index 5e6e3a5eb51..625dc19a57c 100644
--- a/frontend/src/lib/i18n/en.json
+++ b/frontend/src/lib/i18n/en.json
@@ -611,7 +611,7 @@
"spawn_neuron": "Spawn Neuron",
"spawn": "Spawn",
"stake_maturity_disabled_tooltip": "Currently, you do not have any maturity available to stake into this neuron.",
- "disburse_maturity_disabled_tooltip": "Currently, you do not have any maturity available to disburse.",
+ "disburse_maturity_disabled_tooltip": "You do not have enough maturity to disburse. The minimum is: $fee.",
"stake_maturity_tooltip": "Merge Maturity has been replaced by Stake Maturity. Learn more.",
"start_dissolve_description": "This will cause your neuron to lose its age bonus.\nAre you sure you wish to continue?",
"stop_dissolve_description": "Are you sure you want to stop the dissolve process?",
diff --git a/frontend/src/lib/pages/SnsNeuronDetail.svelte b/frontend/src/lib/pages/SnsNeuronDetail.svelte
index 4f10a9037ab..b4df6eb7292 100644
--- a/frontend/src/lib/pages/SnsNeuronDetail.svelte
+++ b/frontend/src/lib/pages/SnsNeuronDetail.svelte
@@ -179,7 +179,10 @@
{token}
/>
-
+
* Is the maturity of the neuron bigger than zero - i.e. has the neuron staked maturity?
* @param {SnsNeuron} neuron
*/
-export const hasEnoughMaturityToStakeOrDisburse = (
+export const hasEnoughMaturityToStake = (
neuron: SnsNeuron | null | undefined
): boolean => (neuron?.maturity_e8s_equivalent ?? BigInt(0)) > BigInt(0);
+/**
+ * Is the maturity of the neuron bigger than the transaction fee?
+ * @param {SnsNeuron} neuron
+ * @param {bigint} feeE8s
+ */
+export const hasEnoughMaturityToDisburse = ({
+ neuron: { maturity_e8s_equivalent },
+ feeE8s,
+}: {
+ feeE8s: bigint;
+ neuron: SnsNeuron;
+}): boolean => maturity_e8s_equivalent >= feeE8s;
+
/**
* Does the neuron has staked maturity?
* @param neuron
diff --git a/frontend/src/tests/lib/components/neuron-detail/actions/DisburseMaturityButton.spec.ts b/frontend/src/tests/lib/components/neuron-detail/actions/DisburseMaturityButton.spec.ts
index c4953bb32c4..2039334bbe5 100644
--- a/frontend/src/tests/lib/components/neuron-detail/actions/DisburseMaturityButton.spec.ts
+++ b/frontend/src/tests/lib/components/neuron-detail/actions/DisburseMaturityButton.spec.ts
@@ -8,10 +8,10 @@ import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
import { render } from "@testing-library/svelte";
describe("DisburseMaturityButton", () => {
- const renderComponent = (enoughMaturity) => {
+ const renderComponent = (disabledText) => {
const { container } = render(DisburseMaturityButton, {
props: {
- enoughMaturity,
+ disabledText,
},
});
return DisburseMaturityButtonPo.under(new JestPageObjectElement(container));
@@ -22,19 +22,19 @@ describe("DisburseMaturityButton", () => {
});
it("renders disburse maturity cta", async () => {
- const po = renderComponent(true);
+ const po = renderComponent(undefined);
expect(await po.isPresent()).toBe(true);
});
it("should be enabled", async () => {
- const po = renderComponent(true);
+ const po = renderComponent(undefined);
expect(await po.isDisabled()).toBe(false);
});
it("should be disabled", async () => {
- const po = renderComponent(false);
+ const po = renderComponent("Disabled Text");
expect(await po.isDisabled()).toBe(true);
});
diff --git a/frontend/src/tests/lib/components/sns-neuron-detail/SnsAvailableMaturityItemAction.spec.ts b/frontend/src/tests/lib/components/sns-neuron-detail/SnsAvailableMaturityItemAction.spec.ts
index 792b8cb6148..cf410527622 100644
--- a/frontend/src/tests/lib/components/sns-neuron-detail/SnsAvailableMaturityItemAction.spec.ts
+++ b/frontend/src/tests/lib/components/sns-neuron-detail/SnsAvailableMaturityItemAction.spec.ts
@@ -29,10 +29,11 @@ describe("SnsAvailableMaturityItemAction", () => {
maturity: 314000000n,
permissions: [controllerPermissions],
});
- const renderComponent = (neuron: SnsNeuron) => {
+ const renderComponent = (neuron: SnsNeuron, feeE8s = 10_000n) => {
const { container } = render(SnsAvailableMaturityItemAction, {
props: {
neuron,
+ feeE8s,
},
});
@@ -89,6 +90,18 @@ describe("SnsAvailableMaturityItemAction", () => {
expect(await po.hasDisburseMaturityButton()).toBe(true);
});
+ it("should render disabled disburse maturity button when maturity is less than fee", async () => {
+ const fee = 100_000_000n;
+ const neuron = createMockSnsNeuron({
+ id: [1],
+ maturity: fee - 1n,
+ permissions: [controllerPermissions],
+ });
+ const po = renderComponent(neuron, fee);
+
+ expect(await po.getDisburseMaturityButtonPo().isDisabled()).toBe(true);
+ });
+
it("should not render stake maturity button if user has no disburse maturity permission", async () => {
const neuron = createMockSnsNeuron({
id: [1],
diff --git a/frontend/src/tests/lib/components/sns-neuron-detail/SnsNeuronMaturitySection.spec.ts b/frontend/src/tests/lib/components/sns-neuron-detail/SnsNeuronMaturitySection.spec.ts
index 46f877838ae..b632b7fccf6 100644
--- a/frontend/src/tests/lib/components/sns-neuron-detail/SnsNeuronMaturitySection.spec.ts
+++ b/frontend/src/tests/lib/components/sns-neuron-detail/SnsNeuronMaturitySection.spec.ts
@@ -3,15 +3,14 @@
*/
import SnsNeuronMaturitySection from "$lib/components/sns-neuron-detail/SnsNeuronMaturitySection.svelte";
-import { mockCanisterId } from "$tests/mocks/canisters.mock";
import { createMockSnsNeuron } from "$tests/mocks/sns-neurons.mock";
import { SnsNeuronMaturitySectionPo } from "$tests/page-objects/SnsNeuronMaturitySection.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
import type { SnsNeuron } from "@dfinity/sns";
import { render } from "@testing-library/svelte";
-import NeuronContextActionsTest from "./SnsNeuronContextTest.svelte";
describe("SnsNeuronMaturitySection", () => {
+ const feeE8s = 10_000n;
const mockNeuron = createMockSnsNeuron({
id: [1],
stakedMaturity: 100_000_000n,
@@ -19,12 +18,10 @@ describe("SnsNeuronMaturitySection", () => {
activeDisbursementsE8s: [200_000_000n],
});
const renderComponent = (neuron: SnsNeuron) => {
- const { container } = render(NeuronContextActionsTest, {
+ const { container } = render(SnsNeuronMaturitySection, {
props: {
neuron,
- passPropNeuron: true,
- rootCanisterId: mockCanisterId,
- testComponent: SnsNeuronMaturitySection,
+ feeE8s,
},
});
diff --git a/frontend/src/tests/lib/components/sns-neuron-detail/actions/SnsDisburseMaturityButton.spec.ts b/frontend/src/tests/lib/components/sns-neuron-detail/actions/SnsDisburseMaturityButton.spec.ts
index 69ce370910d..6c45df18af1 100644
--- a/frontend/src/tests/lib/components/sns-neuron-detail/actions/SnsDisburseMaturityButton.spec.ts
+++ b/frontend/src/tests/lib/components/sns-neuron-detail/actions/SnsDisburseMaturityButton.spec.ts
@@ -6,13 +6,16 @@ import SnsDisburseMaturityButton from "$lib/components/sns-neuron-detail/actions
import { mockSnsNeuron } from "$tests/mocks/sns-neurons.mock";
import { DisburseMaturityButtonPo } from "$tests/page-objects/DisburseMaturityButton.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
+import type { SnsNeuron } from "@dfinity/sns";
import { render } from "@testing-library/svelte";
describe("SnsDisburseMaturityButton", () => {
- const renderComponent = (neuron) => {
+ const fee = 10_000n;
+ const renderComponent = (neuron: SnsNeuron, feeE8s: bigint) => {
const { container } = render(SnsDisburseMaturityButton, {
props: {
neuron,
+ feeE8s,
},
});
return DisburseMaturityButtonPo.under(new JestPageObjectElement(container));
@@ -23,23 +26,32 @@ describe("SnsDisburseMaturityButton", () => {
});
it("should be enabled if enough maturity is available", async () => {
- const po = renderComponent({
- ...mockSnsNeuron,
- maturity_e8s_equivalent: 1n,
- staked_maturity_e8s_equivalent: [],
- });
+ const po = renderComponent(
+ {
+ ...mockSnsNeuron,
+ maturity_e8s_equivalent: fee + 10n,
+ staked_maturity_e8s_equivalent: [],
+ },
+ fee
+ );
expect(await po.isDisabled()).toBe(false);
});
- it("should be disabled if no maturity to disburse", async () => {
- const po = renderComponent({
- ...mockSnsNeuron,
- maturity_e8s_equivalent: 0n,
- staked_maturity_e8s_equivalent: [],
- });
+ it("should be disabled if less maturity than transaction fee", async () => {
+ const po = renderComponent(
+ {
+ ...mockSnsNeuron,
+ maturity_e8s_equivalent: fee - 1n,
+ staked_maturity_e8s_equivalent: [],
+ },
+ fee
+ );
expect(await po.isDisabled()).toBe(true);
+ expect(await po.getTooltipText()).toBe(
+ "You do not have enough maturity to disburse. The minimum is: 0.0001."
+ );
});
it("should open disburse maturity modal", async () => {
diff --git a/frontend/src/tests/lib/utils/sns-neuron.utils.spec.ts b/frontend/src/tests/lib/utils/sns-neuron.utils.spec.ts
index 8423217741a..d5aa4c5f696 100644
--- a/frontend/src/tests/lib/utils/sns-neuron.utils.spec.ts
+++ b/frontend/src/tests/lib/utils/sns-neuron.utils.spec.ts
@@ -28,7 +28,8 @@ import {
getSnsNeuronStake,
getSnsNeuronState,
getSnsNeuronVote,
- hasEnoughMaturityToStakeOrDisburse,
+ hasEnoughMaturityToDisburse,
+ hasEnoughMaturityToStake,
hasEnoughStakeToSplit,
hasPermissionToDisburse,
hasPermissionToDisburseMaturity,
@@ -1361,11 +1362,16 @@ describe("sns-neuron utils", () => {
describe("hasEnoughMaturityToStake", () => {
it("should return true if staked maturity", () => {
- const neuron = {
+ const neuron1 = {
...mockSnsNeuron,
maturity_e8s_equivalent: BigInt(200000000),
};
- expect(hasEnoughMaturityToStakeOrDisburse(neuron)).toBeTruthy();
+ expect(hasEnoughMaturityToStake(neuron1)).toBe(true);
+ const neuron2 = {
+ ...mockSnsNeuron,
+ maturity_e8s_equivalent: 1n,
+ };
+ expect(hasEnoughMaturityToStake(neuron2)).toBe(true);
});
it("should return false if no staked maturity", () => {
@@ -1374,12 +1380,39 @@ describe("sns-neuron utils", () => {
maturity_e8s_equivalent: BigInt(0),
};
- expect(hasEnoughMaturityToStakeOrDisburse(neuron)).toBe(false);
+ expect(hasEnoughMaturityToStake(neuron)).toBe(false);
});
it("should return false when no neuron provided", () => {
- expect(hasEnoughMaturityToStakeOrDisburse(null)).toBe(false);
- expect(hasEnoughMaturityToStakeOrDisburse(undefined)).toBe(false);
+ expect(hasEnoughMaturityToStake(null)).toBe(false);
+ expect(hasEnoughMaturityToStake(undefined)).toBe(false);
+ });
+ });
+
+ describe("hasEnoughMaturityToDisburse", () => {
+ const feeE8s = 10_000n;
+ it("should return true if maturity is more than fee", () => {
+ const neuron = {
+ ...mockSnsNeuron,
+ maturity_e8s_equivalent: feeE8s + 1n,
+ };
+ expect(hasEnoughMaturityToDisburse({ neuron, feeE8s })).toBe(true);
+ });
+
+ it("should return false if maturity less than fee", () => {
+ const neuron = {
+ ...mockSnsNeuron,
+ maturity_e8s_equivalent: feeE8s - 1n,
+ };
+ expect(hasEnoughMaturityToDisburse({ neuron, feeE8s })).toBe(false);
+ });
+
+ it("should return true if maturity is same as fee", () => {
+ const neuron = {
+ ...mockSnsNeuron,
+ maturity_e8s_equivalent: feeE8s,
+ };
+ expect(hasEnoughMaturityToDisburse({ neuron, feeE8s })).toBe(true);
});
});
diff --git a/frontend/src/tests/page-objects/DisburseMaturityButton.page-object.ts b/frontend/src/tests/page-objects/DisburseMaturityButton.page-object.ts
index ec4d6608d34..54f498ac08c 100644
--- a/frontend/src/tests/page-objects/DisburseMaturityButton.page-object.ts
+++ b/frontend/src/tests/page-objects/DisburseMaturityButton.page-object.ts
@@ -1,6 +1,7 @@
import { ButtonPo } from "$tests/page-objects/Button.page-object";
import { BasePageObject } from "$tests/page-objects/base.page-object";
import type { PageObjectElement } from "$tests/types/page-object.types";
+import { TooltipPo } from "./Tooltip.page-object";
export class DisburseMaturityButtonPo extends BasePageObject {
private static readonly TID = "disburse-maturity-button-component";
@@ -18,4 +19,8 @@ export class DisburseMaturityButtonPo extends BasePageObject {
isDisabled(): Promise {
return this.getButton().isDisabled();
}
+
+ getTooltipText(): Promise {
+ return TooltipPo.under(this.root).getText();
+ }
}