Skip to content

Commit

Permalink
Migrate NnsNeuronRewardStatusAction to voting power economics (#6237)
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 `NnsNeuronRewardStatusAction` from constants to
the voting power economics store.

# Changes

- Switch from constants to related stores and utilities.
- Do not show the component without voting power economics available (it
is loaded on dapp initialisation).

# Tests

- The old tests should pass.
- Added: should be hidden w/o params.

# Todos

- [ ] Add entry to changelog (if necessary).
Not necessary.
  • Loading branch information
mstrasinskis authored Jan 23, 2025
1 parent 9c39101 commit 0230d80
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,52 @@
import ConfirmFollowingActionButton from "$lib/components/neuron-detail/actions/ConfirmFollowingActionButton.svelte";
import FollowNeuronsButton from "$lib/components/neuron-detail/actions/FollowNeuronsButton.svelte";
import CommonItemAction from "$lib/components/ui/CommonItemAction.svelte";
import { START_REDUCING_VOTING_POWER_AFTER_SECONDS } from "$lib/constants/neurons.constants";
import { i18n } from "$lib/stores/i18n";
import { secondsToDissolveDelayDuration } from "$lib/utils/date.utils";
import { replacePlaceholders } from "$lib/utils/i18n.utils";
import {
isNeuronFollowingReset,
isNeuronLosingRewards,
secondsUntilLosingRewards,
shouldDisplayRewardLossNotification,
isNeuronFollowingResetVPE,
isNeuronLosingRewardsVPE,
secondsUntilLosingRewardsVPE,
shouldDisplayRewardLossNotificationVPE,
} from "$lib/utils/neuron.utils";
import {
IconCheckCircleFill,
IconError,
IconWarning,
} from "@dfinity/gix-components";
import { type NeuronInfo } from "@dfinity/nns";
import { secondsToDuration } from "@dfinity/utils";
import { nonNullish, secondsToDuration } from "@dfinity/utils";
import {
clearFollowingAfterSecondsStore,
startReducingVotingPowerAfterSecondsStore,
} from "$lib/derived/network-economics.derived";
export let neuron: NeuronInfo;
let isFollowingReset = false;
$: isFollowingReset = isNeuronFollowingReset(neuron);
$: isFollowingReset = isNeuronFollowingResetVPE({
neuron,
startReducingVotingPowerAfterSeconds:
$startReducingVotingPowerAfterSecondsStore,
clearFollowingAfterSeconds: $clearFollowingAfterSecondsStore,
});
let isLosingRewards = false;
$: isLosingRewards = isNeuronLosingRewards(neuron);
$: isLosingRewards = isNeuronLosingRewardsVPE({
neuron,
startReducingVotingPowerAfterSeconds:
$startReducingVotingPowerAfterSecondsStore,
});
let isLosingRewardsSoon = false;
$: isLosingRewardsSoon =
!isLosingRewards && shouldDisplayRewardLossNotification(neuron);
!isLosingRewards &&
shouldDisplayRewardLossNotificationVPE({
neuron,
startReducingVotingPowerAfterSeconds:
$startReducingVotingPowerAfterSecondsStore,
});
let icon: typeof IconError | typeof IconWarning | typeof IconCheckCircleFill;
$: icon =
Expand All @@ -48,15 +65,26 @@
? $i18n.neuron_detail.reward_status_losing_soon
: $i18n.neuron_detail.reward_status_active;
const getDescription = (neuron: NeuronInfo): string => {
const getDescription = ({
neuron,
startReducingVotingPowerAfterSeconds,
}: {
neuron: NeuronInfo;
startReducingVotingPowerAfterSeconds: bigint;
}): string => {
if (isFollowingReset)
return $i18n.neuron_detail.reward_status_inactive_reset_description;
if (isLosingRewards)
return $i18n.neuron_detail.reward_status_inactive_description;
const timeUntilLoss = secondsToDuration({
seconds: BigInt(secondsUntilLosingRewards(neuron)),
seconds: BigInt(
secondsUntilLosingRewardsVPE({
neuron,
startReducingVotingPowerAfterSeconds,
})
),
i18n: $i18n.time,
});
return replacePlaceholders(
Expand All @@ -66,46 +94,50 @@
}
);
};
const tooltipText = replacePlaceholders($i18n.losing_rewards.description, {
$period: secondsToDissolveDelayDuration(
BigInt(START_REDUCING_VOTING_POWER_AFTER_SECONDS)
),
});
</script>

<CommonItemAction
testId="nns-neuron-reward-status-action-component"
{tooltipText}
tooltipId="neuron-reward-status-icon"
>
<span
slot="icon"
class="icon"
class:isLosingRewards
class:isLosingRewardsSoon
>
<svelte:component this={icon} />
</span>
<span slot="title" data-tid="state-title">
{title}
</span>

<span
slot="subtitle"
class="description"
class:negative={isLosingRewards || isLosingRewardsSoon}
data-tid="state-description"
{#if nonNullish($startReducingVotingPowerAfterSecondsStore) && nonNullish($clearFollowingAfterSecondsStore)}
<CommonItemAction
testId="nns-neuron-reward-status-action-component"
tooltipText={replacePlaceholders($i18n.losing_rewards.description, {
$period: secondsToDissolveDelayDuration(
$startReducingVotingPowerAfterSecondsStore
),
})}
tooltipId="neuron-reward-status-icon"
>
{getDescription(neuron)}
</span>

{#if isFollowingReset}
<FollowNeuronsButton variant="secondary" />
{:else}
<ConfirmFollowingActionButton {neuron} />
{/if}
</CommonItemAction>
<span
slot="icon"
class="icon"
class:isLosingRewards
class:isLosingRewardsSoon
>
<svelte:component this={icon} />
</span>
<span slot="title" data-tid="state-title">
{title}
</span>

<span
slot="subtitle"
class="description"
class:negative={isLosingRewards || isLosingRewardsSoon}
data-tid="state-description"
>
{getDescription({
neuron,
startReducingVotingPowerAfterSeconds:
$startReducingVotingPowerAfterSecondsStore,
})}
</span>

{#if isFollowingReset}
<FollowNeuronsButton variant="secondary" />
{:else}
<ConfirmFollowingActionButton {neuron} />
{/if}
</CommonItemAction>
{/if}

<style lang="scss">
.icon {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {
SECONDS_IN_HALF_YEAR,
SECONDS_IN_MONTH,
} from "$lib/constants/constants";
import { networkEconomicsStore } from "$lib/stores/network-economics.store";
import { nowInSeconds } from "$lib/utils/date.utils";
import NnsNeuronRewardStatusActionTest from "$tests/lib/components/neuron-detail/NnsNeuronRewardStatusActionTest.svelte";
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 { NnsNeuronRewardStatusActionPo } from "$tests/page-objects/NnsNeuronRewardStatusAction.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
Expand All @@ -27,6 +29,11 @@ describe("NnsNeuronRewardStatusAction", () => {

beforeEach(() => {
vi.useFakeTimers().setSystemTime("2024-01-01");

networkEconomicsStore.setParameters({
parameters: mockNetworkEconomics,
certified: true,
});
});

it("should render active neuron state", async () => {
Expand All @@ -47,6 +54,20 @@ describe("NnsNeuronRewardStatusAction", () => {
expect(await po.getFollowNeuronsButtonPo().isPresent()).toBe(false);
});

it("should not render active neuron state w/o voting power economics params", async () => {
const testNeuron = {
...mockNeuron,
fullNeuron: {
...mockFullNeuron,
votingPowerRefreshedTimestampSeconds: BigInt(nowInSeconds()),
},
};
networkEconomicsStore.reset();
const po = renderComponent(testNeuron);

expect(await po.isPresent()).toBe(false);
});

it("should render losing soon neuron state", async () => {
const tenDays = 10;
const testNeuron = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import NnsNeuronVotingPowerSection from "$lib/components/neuron-detail/NnsNeuronVotingPowerSection.svelte";
import { NNS_MINIMUM_DISSOLVE_DELAY_TO_VOTE } from "$lib/constants/neurons.constants";
import { overrideFeatureFlagsStore } from "$lib/stores/feature-flags.store";
import { networkEconomicsStore } from "$lib/stores/network-economics.store";
import NeuronContextActionsTest from "$tests/lib/components/neuron-detail/NeuronContextActionsTest.svelte";
import { mockNetworkEconomics } from "$tests/mocks/network-economics.mock";
import { mockFullNeuron, mockNeuron } from "$tests/mocks/neurons.mock";
import { NnsNeuronVotingPowerSectionPo } from "$tests/page-objects/NnsNeuronVotingPowerSection.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
Expand Down Expand Up @@ -150,6 +152,10 @@ describe("NnsStakeItemAction", () => {
"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION",
true
);
networkEconomicsStore.setParameters({
parameters: mockNetworkEconomics,
certified: true,
});
const po = renderComponent(mockNeuron);

expect(await po.getNnsNeuronRewardStatusActionPo().isPresent()).toBe(true);
Expand Down

0 comments on commit 0230d80

Please sign in to comment.