Skip to content

Commit

Permalink
NNS1-3094: Add stake field to TableProject (#5206)
Browse files Browse the repository at this point in the history
# Motivation

We want to show total stake in the staking projects table.
In this PR we just add the data but don't show it in the UI yet.

# Changes

1. Add `stake` field to `TableProject` type.
2. Calculate the stake for each project in `getTableProjects`.

# Tests

1. Unit tests updated.
2. Tested manually in a branch including UI changes.

# Todos

- [ ] Add entry to changelog (if necessary).
not yet
  • Loading branch information
dskloetd authored Jul 17, 2024
1 parent 63d64c8 commit 099ec7f
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 14 deletions.
2 changes: 2 additions & 0 deletions frontend/src/lib/types/staking.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { ResponsiveTableColumn } from "$lib/types/responsive-table";
import type { TokenAmountV2 } from "@dfinity/utils";

export type TableProject = {
rowHref?: string;
domKey: string;
title: string;
logo: string;
neuronCount: number | undefined;
stake: TokenAmountV2 | undefined;
};

export type ProjectsTableColumn = ResponsiveTableColumn<TableProject>;
95 changes: 87 additions & 8 deletions frontend/src/lib/utils/staking.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,88 @@ import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants";
import type { TableProject } from "$lib/types/staking";
import type { Universe } from "$lib/types/universe";
import { buildNeuronsUrl } from "$lib/utils/navigation.utils";
import { hasValidStake as nnsHasValidStake } from "$lib/utils/neuron.utils";
import { hasValidStake as snsHasValidStake } from "$lib/utils/sns-neuron.utils";
import {
neuronStake,
hasValidStake as nnsHasValidStake,
} from "$lib/utils/neuron.utils";
import {
getSnsNeuronStake,
hasValidStake as snsHasValidStake,
} from "$lib/utils/sns-neuron.utils";
import type { NeuronInfo } from "@dfinity/nns";
import type { SnsNeuron } from "@dfinity/sns";
import { ICPToken, TokenAmountV2, isNullish, type Token } from "@dfinity/utils";

const getNnsNeuronCountAndStake = (
nnsNeurons: NeuronInfo[] | undefined
): {
neuronCount: number | undefined;
stake: bigint | undefined;
token: Token;
} => {
const neurons = nnsNeurons?.filter(nnsHasValidStake);
const stake = neurons?.reduce((acc, neuron) => acc + neuronStake(neuron), 0n);
return {
neuronCount: neurons?.length,
stake,
token: ICPToken,
};
};

const getSnsNeuronCountAndStake = ({
universe,
snsNeurons,
}: {
universe: Universe;
snsNeurons: { [rootCanisterId: string]: { neurons: SnsNeuron[] } };
}): {
neuronCount: number | undefined;
stake: bigint | undefined;
token: Token | undefined;
} => {
const neurons =
snsNeurons[universe.canisterId]?.neurons.filter(snsHasValidStake);
const stake = neurons?.reduce(
(acc, neuron) => acc + getSnsNeuronStake(neuron),
0n
);
const token = universe.summary?.token;
return {
neuronCount: neurons?.length,
stake,
token,
};
};

const getNeuronCountAndStake = ({
isSignedIn,
universe,
nnsNeurons,
snsNeurons,
}: {
isSignedIn: boolean;
universe: Universe;
nnsNeurons: NeuronInfo[] | undefined;
snsNeurons: { [rootCanisterId: string]: { neurons: SnsNeuron[] } };
}): { neuronCount: number | undefined; stake: TokenAmountV2 | undefined } => {
if (!isSignedIn) {
return { neuronCount: undefined, stake: undefined };
}
const { neuronCount, stake, token } =
universe.canisterId === OWN_CANISTER_ID_TEXT
? getNnsNeuronCountAndStake(nnsNeurons)
: getSnsNeuronCountAndStake({ snsNeurons, universe });
return {
neuronCount,
stake:
isNullish(stake) || isNullish(token)
? undefined
: TokenAmountV2.fromUlps({
amount: stake,
token,
}),
};
};

export const getTableProjects = ({
universes,
Expand All @@ -19,12 +97,12 @@ export const getTableProjects = ({
snsNeurons: { [rootCanisterId: string]: { neurons: SnsNeuron[] } };
}): TableProject[] => {
return universes.map((universe) => {
const neuronCount = !isSignedIn
? undefined
: universe.canisterId === OWN_CANISTER_ID_TEXT
? nnsNeurons?.filter(nnsHasValidStake).length
: snsNeurons[universe.canisterId]?.neurons.filter(snsHasValidStake)
.length;
const { neuronCount, stake } = getNeuronCountAndStake({
isSignedIn,
universe,
nnsNeurons,
snsNeurons,
});
const rowHref =
(neuronCount ?? 0) > 0
? buildNeuronsUrl({ universe: universe.canisterId })
Expand All @@ -35,6 +113,7 @@ export const getTableProjects = ({
title: universe.title,
logo: universe.logo,
neuronCount,
stake,
};
});
};
52 changes: 46 additions & 6 deletions frontend/src/tests/lib/utils/staking.utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,30 @@ import type { Universe } from "$lib/types/universe";
import { getTableProjects } from "$lib/utils/staking.utils";
import { mockNeuron } from "$tests/mocks/neurons.mock";
import { createMockSnsNeuron } from "$tests/mocks/sns-neurons.mock";
import { principal } from "$tests/mocks/sns-projects.mock";
import {
mockSnsToken,
mockSummary,
principal,
} from "$tests/mocks/sns-projects.mock";
import { ICPToken, TokenAmountV2 } from "@dfinity/utils";

describe("staking.utils", () => {
describe("getTableProjects", () => {
const universeId2 = principal(2).toText();

const nnsUniverse = {
const nnsUniverse: Universe = {
canisterId: OWN_CANISTER_ID_TEXT,
title: "Internet Computer",
logo: IC_LOGO_ROUNDED,
};

const snsUniverse = {
const snsUniverse: Universe = {
canisterId: universeId2,
title: "title2",
logo: "logo2",
summary: mockSummary.override({
token: mockSnsToken,
}),
};

const nnsHref = `/neurons/?u=${OWN_CANISTER_ID_TEXT}`;
Expand All @@ -31,6 +39,10 @@ describe("staking.utils", () => {
title: "Internet Computer",
logo: IC_LOGO_ROUNDED,
neuronCount: 0,
stake: TokenAmountV2.fromUlps({
amount: 0n,
token: ICPToken,
}),
};

const defaultExpectedSnsTableProject = {
Expand All @@ -39,6 +51,10 @@ describe("staking.utils", () => {
title: "title2",
logo: "logo2",
neuronCount: 0,
stake: TokenAmountV2.fromUlps({
amount: 0n,
token: mockSnsToken,
}),
};

const nnsNeuronWithStake = {
Expand Down Expand Up @@ -87,11 +103,15 @@ describe("staking.utils", () => {
]);
});

it("should include number of NNS neurons", () => {
it("should include info for NNS neurons", () => {
const tableProjects = getTableProjects({
universes: [nnsUniverse],
isSignedIn: true,
nnsNeurons: [mockNeuron, mockNeuron, mockNeuron],
nnsNeurons: [
nnsNeuronWithStake,
nnsNeuronWithStake,
nnsNeuronWithStake,
],
snsNeurons: {},
});

Expand All @@ -100,11 +120,15 @@ describe("staking.utils", () => {
...defaultExpectedNnsTableProject,
rowHref: nnsHref,
neuronCount: 3,
stake: TokenAmountV2.fromUlps({
amount: 3n * nnsNeuronWithStake.fullNeuron.cachedNeuronStake,
token: ICPToken,
}),
},
]);
});

it("should include number of SNS neurons", () => {
it("should include info for SNS neurons", () => {
const tableProjects = getTableProjects({
universes: [snsUniverse],
isSignedIn: true,
Expand All @@ -121,6 +145,10 @@ describe("staking.utils", () => {
...defaultExpectedSnsTableProject,
rowHref: snsHref,
neuronCount: 2,
stake: TokenAmountV2.fromUlps({
amount: 2n * snsNeuronWithStake.cached_neuron_stake_e8s,
token: mockSnsToken,
}),
},
]);
});
Expand All @@ -142,6 +170,10 @@ describe("staking.utils", () => {
...defaultExpectedNnsTableProject,
rowHref: nnsHref,
neuronCount: 1,
stake: TokenAmountV2.fromUlps({
amount: nnsNeuronWithStake.fullNeuron.cachedNeuronStake,
token: ICPToken,
}),
},
]);
});
Expand All @@ -168,6 +200,10 @@ describe("staking.utils", () => {
...defaultExpectedSnsTableProject,
rowHref: snsHref,
neuronCount: 1,
stake: TokenAmountV2.fromUlps({
amount: snsNeuronWithStake.cached_neuron_stake_e8s,
token: mockSnsToken,
}),
},
]);
});
Expand All @@ -189,11 +225,13 @@ describe("staking.utils", () => {
...defaultExpectedNnsTableProject,
rowHref: undefined,
neuronCount: undefined,
stake: undefined,
},
{
...defaultExpectedSnsTableProject,
rowHref: undefined,
neuronCount: undefined,
stake: undefined,
},
]);
});
Expand All @@ -213,11 +251,13 @@ describe("staking.utils", () => {
...defaultExpectedNnsTableProject,
rowHref: undefined,
neuronCount: undefined,
stake: undefined,
},
{
...defaultExpectedSnsTableProject,
rowHref: undefined,
neuronCount: undefined,
stake: undefined,
},
]);
});
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/tests/mocks/staking.mock.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import IC_LOGO_ROUNDED from "$lib/assets/icp-rounded.svg";
import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants";
import type { TableProject } from "$lib/types/staking";
import { ICPToken, TokenAmountV2 } from "@dfinity/utils";

export const mockTableProject: TableProject = {
rowHref: `/neurons/?u=${OWN_CANISTER_ID_TEXT}`,
domKey: OWN_CANISTER_ID_TEXT,
title: "Internet Computer",
logo: IC_LOGO_ROUNDED,
neuronCount: 0,
stake: TokenAmountV2.fromUlps({
amount: 100_000_000n,
token: ICPToken,
}),
};

0 comments on commit 099ec7f

Please sign in to comment.