diff --git a/frontend/src/lib/components/neuron-detail/actions/SpawnNeuronButton.svelte b/frontend/src/lib/components/neuron-detail/actions/SpawnNeuronButton.svelte index 12181e1a53b..a2ca51ce2a8 100644 --- a/frontend/src/lib/components/neuron-detail/actions/SpawnNeuronButton.svelte +++ b/frontend/src/lib/components/neuron-detail/actions/SpawnNeuronButton.svelte @@ -3,7 +3,7 @@ import { E8S_PER_ICP } from "$lib/constants/icp.constants"; import { MIN_NEURON_STAKE, - SPAWN_VARIANCE_PERCENTAGE, + MATURITY_MODULATION_VARIANCE_PERCENTAGE, } from "$lib/constants/neurons.constants"; import { i18n } from "$lib/stores/i18n"; import { formatNumber, formatPercentage } from "$lib/utils/format.utils"; @@ -49,17 +49,22 @@ $i18n.neuron_detail.spawn_neuron_disabled_tooltip, { $amount: formatNumber( - MIN_NEURON_STAKE / E8S_PER_ICP / SPAWN_VARIANCE_PERCENTAGE, + MIN_NEURON_STAKE / + E8S_PER_ICP / + MATURITY_MODULATION_VARIANCE_PERCENTAGE, { minFraction: 4, maxFraction: 4 } ), $min: formatNumber(MIN_NEURON_STAKE / E8S_PER_ICP, { minFraction: 0, maxFraction: 0, }), - $varibility: formatPercentage(SPAWN_VARIANCE_PERCENTAGE, { - minFraction: 0, - maxFraction: 0, - }), + $varibility: formatPercentage( + MATURITY_MODULATION_VARIANCE_PERCENTAGE, + { + minFraction: 0, + maxFraction: 0, + } + ), } )} > @@ -69,3 +74,4 @@ {/if} +MATURITY_MODULATION_VARIANCE_PERCENTAGEMATURITY_MODULATION_VARIANCE_PERCENTAGE diff --git a/frontend/src/lib/constants/neurons.constants.ts b/frontend/src/lib/constants/neurons.constants.ts index adb9db8e1a7..3639eb5826f 100644 --- a/frontend/src/lib/constants/neurons.constants.ts +++ b/frontend/src/lib/constants/neurons.constants.ts @@ -6,7 +6,7 @@ import { E8S_PER_ICP } from "./icp.constants"; export const MAX_NEURONS_MERGED = 2; export const MIN_NEURON_STAKE = E8S_PER_ICP; export const MAX_CONCURRENCY = 10; -export const SPAWN_VARIANCE_PERCENTAGE = 0.95; +export const MATURITY_MODULATION_VARIANCE_PERCENTAGE = 0.95; // Neuron ids are random u64. Max digits of a u64 is 20. export const MAX_NEURON_ID_DIGITS = 20; diff --git a/frontend/src/lib/modals/sns/neurons/SnsDisburseMaturityModal.svelte b/frontend/src/lib/modals/sns/neurons/SnsDisburseMaturityModal.svelte index 06022fbe93e..e1f6cd75ec1 100644 --- a/frontend/src/lib/modals/sns/neurons/SnsDisburseMaturityModal.svelte +++ b/frontend/src/lib/modals/sns/neurons/SnsDisburseMaturityModal.svelte @@ -5,7 +5,10 @@ import type { SnsNeuron, SnsNeuronId } from "@dfinity/sns"; import type { Principal } from "@dfinity/principal"; import { disburseMaturity as disburseMaturityService } from "$lib/services/sns-neurons.services"; - import { formattedMaturity } from "$lib/utils/sns-neuron.utils"; + import { + formattedMaturity, + minimumAmountToDisburseMaturity, + } from "$lib/utils/sns-neuron.utils"; import DisburseMaturityModal from "$lib/modals/neurons/DisburseMaturityModal.svelte"; import { snsProjectMainAccountStore } from "$lib/derived/sns/sns-project-accounts.derived"; import { shortenWithMiddleEllipsis } from "$lib/utils/format.utils"; @@ -28,10 +31,10 @@ let token: IcrcTokenMetadata | undefined; $: token = $tokensStore[rootCanisterId.toText()]?.token; - // 99% of users will disburse more than the transaction fee. - // We don't want a possible error fetching the fee to disrupt the whole flow. - let minimumAmountE8s = 0n; - $: minimumAmountE8s = token?.fee ?? 0n; + let minimumAmountE8s: bigint; + // Token is loaded with all the projects from the aggregator. + // Therefore, if the user made it here, it's present. + $: minimumAmountE8s = minimumAmountToDisburseMaturity(token?.fee ?? 0n); const dispatcher = createEventDispatcher(); const close = () => dispatcher("nnsClose"); diff --git a/frontend/src/lib/utils/neuron.utils.ts b/frontend/src/lib/utils/neuron.utils.ts index 1994116d1c8..f805448f70b 100644 --- a/frontend/src/lib/utils/neuron.utils.ts +++ b/frontend/src/lib/utils/neuron.utils.ts @@ -12,9 +12,9 @@ import { import { AGE_MULTIPLIER, DISSOLVE_DELAY_MULTIPLIER, + MATURITY_MODULATION_VARIANCE_PERCENTAGE, MAX_NEURONS_MERGED, MIN_NEURON_STAKE, - SPAWN_VARIANCE_PERCENTAGE, TOPICS_TO_FOLLOW_NNS, } from "$lib/constants/neurons.constants"; import { DEPRECATED_TOPICS } from "$lib/constants/proposals.constants"; @@ -499,7 +499,10 @@ export const isEnoughMaturityToSpawn = ({ const maturitySelected = Math.floor( (Number(fullNeuron.maturityE8sEquivalent) * percentage) / 100 ); - return maturitySelected >= MIN_NEURON_STAKE / SPAWN_VARIANCE_PERCENTAGE; + return ( + maturitySelected >= + MIN_NEURON_STAKE / MATURITY_MODULATION_VARIANCE_PERCENTAGE + ); }; export const isSpawning = (neuron: NeuronInfo): boolean => diff --git a/frontend/src/lib/utils/sns-neuron.utils.ts b/frontend/src/lib/utils/sns-neuron.utils.ts index 01abda4999a..2e3ba83cd6c 100644 --- a/frontend/src/lib/utils/sns-neuron.utils.ts +++ b/frontend/src/lib/utils/sns-neuron.utils.ts @@ -1,3 +1,4 @@ +import { MATURITY_MODULATION_VARIANCE_PERCENTAGE } from "$lib/constants/neurons.constants"; import { HOTKEY_PERMISSIONS, MANAGE_HOTKEY_PERMISSIONS, @@ -981,3 +982,11 @@ export const totalDisbursingMaturity = ({ (acc, disbursement) => acc + disbursement.amount_e8s, BigInt(0) ); + +/** + * The governance canister checks that the amount to disburse in the worst case (of the maturity modulation) is bigger than the transaction fee. + * + * Source: https://sourcegraph.com/github.com/dfinity/ic/-/blob/rs/sns/governance/src/governance.rs?L1651 + */ +export const minimumAmountToDisburseMaturity = (fee: bigint): bigint => + BigInt(Math.round(Number(fee) / MATURITY_MODULATION_VARIANCE_PERCENTAGE)); 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 d5aa4c5f696..f225032dece 100644 --- a/frontend/src/tests/lib/utils/sns-neuron.utils.spec.ts +++ b/frontend/src/tests/lib/utils/sns-neuron.utils.spec.ts @@ -47,6 +47,7 @@ import { isUserHotkey, isVesting, minNeuronSplittable, + minimumAmountToDisburseMaturity, needsRefresh, neuronAge, nextMemo, @@ -2492,4 +2493,14 @@ describe("sns-neuron utils", () => { expect(totalDisbursingMaturity(neuron)).toBe(0n); }); }); + + describe("minimumAmountToDisburseMaturity", () => { + it("returns worst case of maturity modulation", () => { + expect(minimumAmountToDisburseMaturity(10_000n)).toBe(10526n); + }); + + it("returns 0 if fee is 0", () => { + expect(minimumAmountToDisburseMaturity(0n)).toBe(0n); + }); + }); });