Skip to content

Commit

Permalink
Migrate getNeuronTags to voting power economics (#6241)
Browse files Browse the repository at this point in the history
# Motivation

Currently, the voting power parameters are hardcoded as half a year and
one month. However, actual values are already being retrieved from the
API (they are the same for now but may be updated in the future). The
general idea is that if the parameters are unavailable, we consider the
neuron active.

In this PR, we migrate the getNeuronTags function (and its callers) to
the voting power economics utilities.

# Changes

- Switch to ...VPE utils in getNeuronTags
([changes](https://github.com/dfinity/nns-dapp/pull/6241/files#diff-b570daeb2a65094f5bd1d1b823bcf3025de7af68555903e0c2061d2f8b029e8fR555-R581)).
- Update all callers to provide startReducingVotingPowerAfterSeconds
argument.

# Tests

- Updated.

# Todos

- [ ] Add entry to changelog (if necessary).
Not necessary.
  • Loading branch information
mstrasinskis authored Jan 24, 2025
1 parent 4751a60 commit 1afd87d
Show file tree
Hide file tree
Showing 16 changed files with 283 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
} from "$lib/utils/neuron.utils";
import type { NeuronInfo } from "@dfinity/nns";
import { ICPToken, TokenAmountV2 } from "@dfinity/utils";
import { startReducingVotingPowerAfterSecondsStore } from "$lib/derived/network-economics.derived";
export let neuron: NeuronInfo;
Expand All @@ -39,6 +40,8 @@
identity: $authStore.identity,
accounts: $icpAccountsStore,
i18n: $i18n,
startReducingVotingPowerAfterSeconds:
$startReducingVotingPowerAfterSecondsStore,
});
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { Card, IconRight } from "@dfinity/gix-components";
import type { NeuronInfo } from "@dfinity/nns";
import { nonNullish } from "@dfinity/utils";
import { startReducingVotingPowerAfterSecondsStore } from "$lib/derived/network-economics.derived";
export let neuron: NeuronInfo;
export let onClick: (() => void) | undefined;
Expand All @@ -26,6 +27,8 @@
identity: $authStore.identity,
accounts: $icpAccountsStore,
i18n: $i18n,
startReducingVotingPowerAfterSeconds:
$startReducingVotingPowerAfterSecondsStore,
});
let followees: FolloweesNeuron[];
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lib/components/neurons/NnsNeuronCardTitle.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { i18n } from "$lib/stores/i18n";
import { getNeuronTags, type NeuronTagData } from "$lib/utils/neuron.utils";
import type { NeuronInfo } from "@dfinity/nns";
import { startReducingVotingPowerAfterSecondsStore } from "$lib/derived/network-economics.derived";
export let neuron: NeuronInfo;
export let tagName: "p" | "h3" = "p";
Expand All @@ -15,6 +16,8 @@
identity: $authStore.identity,
accounts: $icpAccountsStore,
i18n: $i18n,
startReducingVotingPowerAfterSeconds:
$startReducingVotingPowerAfterSecondsStore,
});
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import type { NeuronInfo } from "@dfinity/nns";
import { nonNullish } from "@dfinity/utils";
import { createEventDispatcher } from "svelte";
import { startReducingVotingPowerAfterSecondsStore } from "$lib/derived/network-economics.derived";
export let defaultSelectedNeuron: NeuronInfo | null = null;
export let makePublic: boolean;
Expand Down Expand Up @@ -126,6 +127,8 @@
identity: $authStore.identity,
accounts: $icpAccountsStore,
i18n: $i18n,
startReducingVotingPowerAfterSeconds:
$startReducingVotingPowerAfterSecondsStore,
})}
checked={isNeuronSelected(n)}
on:nnsChange={() => handleCheckboxChange(n)}
Expand Down Expand Up @@ -153,6 +156,8 @@
identity: $authStore.identity,
accounts: $icpAccountsStore,
i18n: $i18n,
startReducingVotingPowerAfterSeconds:
$startReducingVotingPowerAfterSecondsStore,
})}
disabled
/>
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lib/pages/NnsNeurons.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import { getTotalStakeInUsd } from "$lib/utils/staking.utils";
import { IconNeuronsPage, Spinner } from "@dfinity/gix-components";
import { onMount } from "svelte";
import { startReducingVotingPowerAfterSecondsStore } from "$lib/derived/network-economics.derived";
let isLoading = false;
$: isLoading = $neuronsStore.neurons === undefined;
Expand All @@ -35,6 +36,8 @@
i18n: $i18n,
neuronInfos: $definedNeuronsStore,
icpSwapUsdPrices: $icpSwapUsdPricesStore,
startReducingVotingPowerAfterSeconds:
$startReducingVotingPowerAfterSecondsStore,
});
let totalStakeInUsd: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { icpAccountsStore } from "$lib/derived/icp-accounts.derived";
import { icpSwapUsdPricesStore } from "$lib/derived/icp-swap.derived";
import { startReducingVotingPowerAfterSecondsStore } from "$lib/derived/network-economics.derived";
import { definedNeuronsStore } from "$lib/derived/neurons.derived";
import { authStore } from "$lib/stores/auth.store";
import { i18n } from "$lib/stores/i18n";
Expand All @@ -18,20 +19,24 @@ const tableNeuronsToSortStore = derived(
i18n,
definedNeuronsStore,
icpSwapUsdPricesStore,
startReducingVotingPowerAfterSecondsStore,
],
([
$authStore,
$icpAccountsStore,
$i18n,
$definedNeuronsStore,
$icpSwapUsdPricesStore,
$startReducingVotingPowerAfterSecondsStore,
]) => {
const tableNeurons = tableNeuronsFromNeuronInfos({
identity: $authStore.identity,
accounts: $icpAccountsStore,
i18n: $i18n,
neuronInfos: $definedNeuronsStore,
icpSwapUsdPrices: $icpSwapUsdPricesStore,
startReducingVotingPowerAfterSeconds:
$startReducingVotingPowerAfterSecondsStore,
});
return tableNeurons.sort(compareById);
}
Expand Down
41 changes: 36 additions & 5 deletions frontend/src/lib/utils/neuron.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,15 +477,23 @@ export const getNeuronTags = ({
identity,
accounts,
i18n,
startReducingVotingPowerAfterSeconds,
}: {
neuron: NeuronInfo;
identity?: Identity | null;
accounts: IcpAccountsStoreData;
i18n: I18n;
startReducingVotingPowerAfterSeconds: bigint | undefined;
}): NeuronTagData[] => {
const tags: NeuronTagData[] = [];

tags.push(...getNeuronTagsUnrelatedToController({ neuron, i18n }));
tags.push(
...getNeuronTagsUnrelatedToController({
neuron,
i18n,
startReducingVotingPowerAfterSeconds,
})
);

const isHWControlled = isNeuronControlledByHardwareWallet({
neuron,
Expand Down Expand Up @@ -526,9 +534,11 @@ const secondsToMissingRewardsDuration = ({
const getNeuronTagsUnrelatedToController = ({
neuron,
i18n,
startReducingVotingPowerAfterSeconds,
}: {
neuron: NeuronInfo;
i18n: I18n;
startReducingVotingPowerAfterSeconds: bigint | undefined;
}): NeuronTagData[] => {
const tags: NeuronTagData[] = [];

Expand All @@ -542,17 +552,33 @@ const getNeuronTagsUnrelatedToController = ({
tags.push({ text: i18n.neurons.community_fund });
}

if (get(ENABLE_PERIODIC_FOLLOWING_CONFIRMATION)) {
if (isNeuronLosingRewards(neuron)) {
// Skip the "missing rewards" tag when voting power economics not available
if (
nonNullish(startReducingVotingPowerAfterSeconds) &&
get(ENABLE_PERIODIC_FOLLOWING_CONFIRMATION)
) {
if (
isNeuronLosingRewardsVPE({ neuron, startReducingVotingPowerAfterSeconds })
) {
tags.push({
text: i18n.neurons.missing_rewards,
status: "danger",
});
} else if (shouldDisplayRewardLossNotification(neuron)) {
} else if (
shouldDisplayRewardLossNotificationVPE({
neuron,
startReducingVotingPowerAfterSeconds,
})
) {
tags.push({
text: replacePlaceholders(i18n.neurons.missing_rewards_soon, {
$timeLeft: secondsToMissingRewardsDuration({
seconds: BigInt(secondsUntilLosingRewards(neuron)),
seconds: BigInt(
secondsUntilLosingRewardsVPE({
neuron,
startReducingVotingPowerAfterSeconds,
})
),
i18n,
}),
}),
Expand All @@ -569,11 +595,15 @@ export const createNeuronVisibilityRowData = ({
identity,
accounts,
i18n,
startReducingVotingPowerAfterSeconds,
}: {
neuron: NeuronInfo;
identity?: Identity | null;
accounts: IcpAccountsStoreData;
i18n: I18n;
// The function should work w/o voting power economics to not block the visibility functionality.
// In this case only the "Missing Rewards" tag will be missing.
startReducingVotingPowerAfterSeconds: bigint | undefined;
}): NeuronVisibilityRowData => {
return {
neuronId: neuron.neuronId.toString(),
Expand All @@ -587,6 +617,7 @@ export const createNeuronVisibilityRowData = ({
tags: getNeuronTagsUnrelatedToController({
neuron,
i18n,
startReducingVotingPowerAfterSeconds,
}),
uncontrolledNeuronDetails: getNeuronVisibilityRowUncontrolledNeuronDetails({
neuron,
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lib/utils/neurons-table.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ export const tableNeuronsFromNeuronInfos = ({
accounts,
icpSwapUsdPrices,
i18n,
startReducingVotingPowerAfterSeconds,
}: {
neuronInfos: NeuronInfo[];
identity?: Identity | undefined | null;
accounts: IcpAccountsStoreData;
icpSwapUsdPrices: IcpSwapUsdPricesStoreData;
i18n: I18n;
startReducingVotingPowerAfterSeconds: bigint | undefined;
}): TableNeuron[] => {
return neuronInfos.map((neuronInfo) => {
const { neuronId, dissolveDelaySeconds } = neuronInfo;
Expand Down Expand Up @@ -91,6 +93,7 @@ export const tableNeuronsFromNeuronInfos = ({
identity,
accounts,
i18n,
startReducingVotingPowerAfterSeconds,
}),
isPublic: isPublicNeuron(neuronInfo),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import NnsNeuronPageHeading from "$lib/components/neuron-detail/NnsNeuronPageHeading.svelte";
import { CKUSDC_UNIVERSE_CANISTER_ID } from "$lib/constants/ckusdc-canister-ids.constants";
import { SECONDS_IN_YEAR } from "$lib/constants/constants";
import { NNS_MINIMUM_DISSOLVE_DELAY_TO_VOTE } from "$lib/constants/neurons.constants";
import { overrideFeatureFlagsStore } from "$lib/stores/feature-flags.store";
import { icpSwapTickersStore } from "$lib/stores/icp-swap.store";
import { networkEconomicsStore } from "$lib/stores/network-economics.store";
import { nowInSeconds } from "$lib/utils/date.utils";
import { mockIdentity, resetIdentity } from "$tests/mocks/auth.store.mock";
import {
mockHardwareWalletAccount,
mockMainAccount,
} from "$tests/mocks/icp-accounts.store.mock";
import { mockIcpSwapTicker } from "$tests/mocks/icp-swap.mock";
import { mockNetworkEconomics } from "$tests/mocks/network-economics.mock";
import { mockNeuron } from "$tests/mocks/neurons.mock";
import { NnsNeuronPageHeadingPo } from "$tests/page-objects/NnsNeuronPageHeading.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
Expand Down Expand Up @@ -160,6 +164,28 @@ describe("NnsNeuronPageHeading", () => {
expect(await po.getNeuronTags()).toEqual(["Early Contributor Token"]);
});

it("should render 'Missing rewards' tag", async () => {
overrideFeatureFlagsStore.setFlag(
"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION",
true
);
networkEconomicsStore.setParameters({
parameters: mockNetworkEconomics,
certified: true,
});
const po = renderComponent({
...mockNeuron,
fullNeuron: {
...mockNeuron.fullNeuron,
votingPowerRefreshedTimestampSeconds: BigInt(
nowInSeconds() - SECONDS_IN_YEAR
),
},
});

expect(await po.getNeuronTags()).toEqual(["Missing rewards"]);
});

it("should not display USD balance if feature flag is disabled", async () => {
overrideFeatureFlagsStore.setFlag("ENABLE_USD_VALUES_FOR_NEURONS", false);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import NnsLosingRewardsNeuronCard from "$lib/components/neurons/NnsLosingRewardsNeuronCard.svelte";
import { SECONDS_IN_YEAR } from "$lib/constants/constants";
import { overrideFeatureFlagsStore } from "$lib/stores/feature-flags.store";
import { networkEconomicsStore } from "$lib/stores/network-economics.store";
import { nowInSeconds } from "$lib/utils/date.utils";
import { mockIdentity } from "$tests/mocks/auth.store.mock";
import { mockNetworkEconomics } from "$tests/mocks/network-economics.mock";
import { mockFullNeuron, mockNeuron } from "$tests/mocks/neurons.mock";
import { NnsLosingRewardsNeuronCardPo } from "$tests/page-objects/NnsLosingRewardsNeuronCard.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
Expand All @@ -26,6 +31,9 @@ describe("NnsLosingRewardsNeuronCard", () => {
followees: [followNeuronId1],
},
],
votingPowerRefreshedTimestampSeconds: BigInt(
nowInSeconds() - SECONDS_IN_YEAR
),
},
};

Expand Down Expand Up @@ -59,6 +67,20 @@ describe("NnsLosingRewardsNeuronCard", () => {
expect(onClick).toHaveBeenCalledTimes(1);
});

it("should render 'Missing rewards' tag", async () => {
overrideFeatureFlagsStore.setFlag(
"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION",
true
);
networkEconomicsStore.setParameters({
parameters: mockNetworkEconomics,
certified: true,
});

const po = await renderComponent({ neuron });
expect(await po.getNeuronTags()).toEqual(["Missing rewards"]);
});

it("should render following", async () => {
const po = await renderComponent({ neuron });

Expand Down
34 changes: 34 additions & 0 deletions frontend/src/tests/lib/components/neurons/NnsNeuronCard.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import NnsNeuronCard from "$lib/components/neurons/NnsNeuronCard.svelte";
import { SECONDS_IN_YEAR } from "$lib/constants/constants";
import { overrideFeatureFlagsStore } from "$lib/stores/feature-flags.store";
import { networkEconomicsStore } from "$lib/stores/network-economics.store";
import { formatTokenE8s } from "$lib/utils/token.utils";
import { mockIdentity, resetIdentity } from "$tests/mocks/auth.store.mock";
import en from "$tests/mocks/i18n.mock";
import {
mockHardwareWalletAccount,
mockMainAccount,
} from "$tests/mocks/icp-accounts.store.mock";
import { mockNetworkEconomics } from "$tests/mocks/network-economics.mock";
import { mockFullNeuron, mockNeuron } from "$tests/mocks/neurons.mock";
import { NnsNeuronCardPo } from "$tests/page-objects/NnsNeuronCard.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
Expand Down Expand Up @@ -182,6 +185,37 @@ describe("NnsNeuronCard", () => {
expect(await po.getNeuronTags()).toEqual(["Seed"]);
});

it("renders 'Missing rewards' tag", async () => {
overrideFeatureFlagsStore.setFlag(
"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION",
true
);
networkEconomicsStore.setParameters({
parameters: mockNetworkEconomics,
certified: true,
});
setAccountsForTesting({
main: mockMainAccount,
subAccounts: [],
});
const { container } = render(NnsNeuronCard, {
props: {
neuron: {
...mockNeuron,
fullNeuron: {
...mockNeuron.fullNeuron,
votingPowerRefreshedTimestampSeconds: BigInt(
nowInSeconds - SECONDS_IN_YEAR
),
},
},
},
});
const po = NnsNeuronCardPo.under(new JestPageObjectElement(container));

expect(await po.getNeuronTags()).toEqual(["Missing rewards"]);
});

it("renders proper text when status is LOCKED", async () => {
const MORE_THAN_ONE_YEAR = 60 * 60 * 24 * 365 * 1.5;
const { getByText } = render(NnsNeuronCard, {
Expand Down
Loading

0 comments on commit 1afd87d

Please sign in to comment.