Skip to content

Commit

Permalink
Merge branch 'main' into neuron-confirm-following-confirmation
Browse files Browse the repository at this point in the history
# Conflicts:
#	frontend/src/lib/modals/neurons/LosingRewardNeuronsModal.svelte
  • Loading branch information
mstrasinskis committed Jan 7, 2025
2 parents 313bca7 + 43a1a9e commit 4d4b4c7
Show file tree
Hide file tree
Showing 23 changed files with 699 additions and 87 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG-Nns-Dapp-unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ proposal is successful, the changes it released will be moved from this file to

#### Changed

- Allow `dfinity:` token prefix in QR code for ICP payment.

#### Deprecated

#### Removed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Candid for canister `sns_governance` obtained by `scripts/update_ic_commit` from: <https://raw.githubusercontent.com/dfinity/ic/release-2024-12-06_03-16-base/rs/sns/governance/canister/governance.did>
//! Candid for canister `sns_governance` obtained by `scripts/update_ic_commit` from: <https://raw.githubusercontent.com/dfinity/ic/release-2025-01-03_03-07-base/rs/sns/governance/canister/governance.did>
type Account = record {
owner : opt principal;
subaccount : opt Subaccount;
Expand Down Expand Up @@ -158,6 +158,7 @@ type DefiniteCanisterSettingsArgs = record {
wasm_memory_limit : opt nat;
memory_allocation : nat;
compute_allocation : nat;
wasm_memory_threshold : opt nat;
};

type DeregisterDappCanisters = record {
Expand Down Expand Up @@ -376,6 +377,7 @@ type ManageDappCanisterSettings = record {
wasm_memory_limit : opt nat64;
memory_allocation : opt nat64;
compute_allocation : opt nat64;
wasm_memory_threshold : opt nat64;
};

type SnsVersion = record {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Candid for canister `sns_ledger` obtained by `scripts/update_ic_commit` from: <https://raw.githubusercontent.com/dfinity/ic/release-2024-12-06_03-16-base/rs/ledger_suite/icrc1/ledger/ledger.did>
//! Candid for canister `sns_ledger` obtained by `scripts/update_ic_commit` from: <https://raw.githubusercontent.com/dfinity/ic/release-2025-01-03_03-07-base/rs/ledger_suite/icrc1/ledger/ledger.did>
type BlockIndex = nat;
type Subaccount = blob;
// Number of nanoseconds since the UNIX epoch in UTC timezone.
Expand Down
5 changes: 4 additions & 1 deletion declarations/used_by_sns_aggregator/sns_root/sns_root.did
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Candid for canister `sns_root` obtained by `scripts/update_ic_commit` from: <https://raw.githubusercontent.com/dfinity/ic/release-2024-12-06_03-16-base/rs/sns/root/canister/root.did>
//! Candid for canister `sns_root` obtained by `scripts/update_ic_commit` from: <https://raw.githubusercontent.com/dfinity/ic/release-2025-01-03_03-07-base/rs/sns/root/canister/root.did>
type CanisterCallError = record {
code : opt int32;
description : text;
Expand Down Expand Up @@ -62,6 +62,7 @@ type DefiniteCanisterSettings = record {
wasm_memory_limit : opt nat;
memory_allocation : opt nat;
compute_allocation : opt nat;
wasm_memory_threshold : opt nat;
};

type DefiniteCanisterSettingsArgs = record {
Expand All @@ -70,6 +71,7 @@ type DefiniteCanisterSettingsArgs = record {
wasm_memory_limit : opt nat;
memory_allocation : nat;
compute_allocation : nat;
wasm_memory_threshold : opt nat;
};

type FailedUpdate = record {
Expand Down Expand Up @@ -114,6 +116,7 @@ type ManageDappCanisterSettingsRequest = record {
wasm_memory_limit : opt nat64;
memory_allocation : opt nat64;
compute_allocation : opt nat64;
wasm_memory_threshold : opt nat64;
};

type ManageDappCanisterSettingsResponse = record {
Expand Down
3 changes: 2 additions & 1 deletion declarations/used_by_sns_aggregator/sns_swap/sns_swap.did
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Candid for canister `sns_swap` obtained by `scripts/update_ic_commit` from: <https://raw.githubusercontent.com/dfinity/ic/release-2024-12-06_03-16-base/rs/sns/swap/canister/swap.did>
//! Candid for canister `sns_swap` obtained by `scripts/update_ic_commit` from: <https://raw.githubusercontent.com/dfinity/ic/release-2025-01-03_03-07-base/rs/sns/swap/canister/swap.did>
type BuyerState = record {
icp : opt TransferableAmount;
has_created_neuron_recipes : opt bool;
Expand Down Expand Up @@ -54,6 +54,7 @@ type DefiniteCanisterSettingsArgs = record {
wasm_memory_limit : opt nat;
memory_allocation : nat;
compute_allocation : nat;
wasm_memory_threshold : opt nat;
};

type DerivedState = record {
Expand Down
2 changes: 1 addition & 1 deletion declarations/used_by_sns_aggregator/sns_wasm/sns_wasm.did
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Candid for canister `sns_wasm` obtained by `scripts/update_ic_commit` from: <https://raw.githubusercontent.com/dfinity/ic/release-2024-12-06_03-16-base/rs/nns/sns-wasm/canister/sns-wasm.did>
//! Candid for canister `sns_wasm` obtained by `scripts/update_ic_commit` from: <https://raw.githubusercontent.com/dfinity/ic/release-2025-01-03_03-07-base/rs/nns/sns-wasm/canister/sns-wasm.did>
type AddWasmRequest = record {
hash : blob;
wasm : opt SnsWasm;
Expand Down
2 changes: 1 addition & 1 deletion dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@
"CARGO_SORT_VERSION": "1.0.9",
"SNSDEMO_RELEASE": "release-2025-01-03",
"IC_COMMIT_FOR_PROPOSALS": "release-2025-01-03_03-07-base",
"IC_COMMIT_FOR_SNS_AGGREGATOR": "release-2024-12-06_03-16-base"
"IC_COMMIT_FOR_SNS_AGGREGATOR": "release-2025-01-03_03-07-base"
},
"packtool": ""
}
Expand Down
11 changes: 4 additions & 7 deletions frontend/src/lib/api/canisters.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { CYCLES_MINTING_CANISTER_ID } from "$lib/constants/canister-ids.constant
import { MAX_CANISTER_NAME_LENGTH } from "$lib/constants/canisters.constants";
import { HOST } from "$lib/constants/environment.constants";
import { ApiErrorKey } from "$lib/types/api.errors";
import { getCanisterCreationCmcAccountIdentifierHex } from "$lib/utils/canisters.utils";
import { nowInBigIntNanoSeconds } from "$lib/utils/date.utils";
import { logWithTimestamp } from "$lib/utils/dev.utils";
import { poll, pollingLimit } from "$lib/utils/utils";
Expand Down Expand Up @@ -232,20 +233,16 @@ export const createCanister = async ({

const { cmc, nnsDapp } = await canisters(identity);
const principal = identity.getPrincipal();
const toSubAccount = principalToSubAccount(principal);
// To create a canister you need to send ICP to an account owned by the CMC, so that the CMC can burn those funds.
// To ensure everyone uses a unique address, the intended controller of the new canister is used to calculate the subaccount.
const recipient = AccountIdentifier.fromPrincipal({
principal: CYCLES_MINTING_CANISTER_ID,
subAccount: SubAccount.fromBytes(toSubAccount) as SubAccount,
const recipientHex = getCanisterCreationCmcAccountIdentifierHex({
controller: principal,
});

const createdAt = nowInBigIntNanoSeconds();
// Transfer the funds
const blockHeight = await sendICP({
memo: CREATE_CANISTER_MEMO,
identity,
to: recipient.toHex(),
to: recipientHex,
amount,
fromSubAccount,
createdAt,
Expand Down
13 changes: 12 additions & 1 deletion frontend/src/lib/modals/transaction/QrWizardModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,20 @@
const { token, identifier, amount } = payment;
// NOTE: Coinbase incorrectly uses "dfinity" instead of "icp" as the token
// symbol in the QR payment URI. We've asked them to fix this but so far
// they haven't. So we work around this by allowing "dfinity" in place of
// "icp".
const coinbaseIcpTokenSymbol = "dfinity";
const correctIcpTokenSymbol = "icp";
if (
nonNullish(requiredToken) &&
token.toLowerCase() !== requiredToken.symbol.toLowerCase()
!(
token.toLowerCase() === requiredToken.symbol.toLowerCase() ||
(token.toLowerCase() === coinbaseIcpTokenSymbol &&
requiredToken.symbol.toLowerCase() === correctIcpTokenSymbol)
)
) {
toastsError({
labelKey: "error.qrcode_token_incompatible",
Expand Down
24 changes: 7 additions & 17 deletions frontend/src/lib/services/sns-accounts-balance.services.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { toastsError } from "$lib/stores/toasts.store";
import type { RootCanisterId, RootCanisterIdText } from "$lib/types/sns";
import type { RootCanisterId } from "$lib/types/sns";
import { loadSnsAccounts } from "./sns-accounts.services";

/**
Expand All @@ -9,28 +9,18 @@ import { loadSnsAccounts } from "./sns-accounts.services";
*
* @param {rootCanisterIds: RootCanisterId[], excludeRootCanisterIds?: RootCanisterIdText[]} params
* @param {RootCanisterId[]} params.rootCanisterIds The list of root canister ids - Sns projects - for which the balance of the accounts should be fetched.
* @param {RootCanisterIdText[] | undefined} params.excludeRootCanisterIds As the balance is also loaded by loadSnsAccounts() - to perform query and UPDATE call - this variable can be used to avoid to perform unnecessary query and per extension to override data in the balance store.
*/
export const uncertifiedLoadSnsesAccountsBalances = async ({
rootCanisterIds,
excludeRootCanisterIds = [],
}: {
rootCanisterIds: RootCanisterId[];
excludeRootCanisterIds?: RootCanisterIdText[];
}): Promise<void> => {
const results: PromiseSettledResult<[void]>[] = await Promise.allSettled(
(
rootCanisterIds.filter(
(rootCanisterId) =>
!excludeRootCanisterIds.includes(rootCanisterId.toText())
) ?? []
).map((rootCanisterId) =>
Promise.all([
loadSnsAccounts({
rootCanisterId,
strategy: "query",
}),
])
const results: PromiseSettledResult<void>[] = await Promise.allSettled(
rootCanisterIds.map((rootCanisterId) =>
loadSnsAccounts({
rootCanisterId,
strategy: "query",
})
)
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,15 @@ import { loadAccounts, loadIcrcToken } from "./icrc-accounts.services";
*
* @param {universeIds: UniverseCanisterId[]; excludeUniverseIds: RootCanisterIdText[] | undefined} params
* @param {UniverseCanisterId[]} params.universeIds The Icrc environment for which the balances should be loaded.
* @param {RootCanisterIdText[] | undefined} params.excludeUniverseIds As the balance is also loaded by loadSnsAccounts() - to perform query and UPDATE call - this variable can be used to avoid to perform unnecessary query and per extension to override data in the balance store.
*/
export const uncertifiedLoadAccountsBalance = async ({
universeIds,
excludeUniverseIds = [],
}: {
universeIds: UniverseCanisterIdText[];
excludeUniverseIds?: UniverseCanisterIdText[] | undefined;
}): Promise<void> => {
const results: PromiseSettledResult<[void, void]>[] =
await Promise.allSettled(
(
universeIds.filter(
(universeId) => !excludeUniverseIds.includes(universeId)
) ?? []
).map((universeId) =>
universeIds.map((universeId) =>
Promise.all([
loadAccounts({
strategy: "query",
Expand Down
19 changes: 18 additions & 1 deletion frontend/src/lib/utils/canisters.utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type { CanisterDetails } from "$lib/canisters/ic-management/ic-management.canister.types";
import { CanisterStatus } from "$lib/canisters/ic-management/ic-management.canister.types";
import type { CanisterDetails as CanisterInfo } from "$lib/canisters/nns-dapp/nns-dapp.types";
import { CYCLES_MINTING_CANISTER_ID } from "$lib/constants/canister-ids.constants";
import { MAX_CANISTER_NAME_LENGTH } from "$lib/constants/canisters.constants";
import { ONE_TRILLION } from "$lib/constants/icp.constants";
import type { AuthStoreData } from "$lib/stores/auth.store";
import type { CanistersStore } from "$lib/stores/canisters.store";
import { i18n } from "$lib/stores/i18n";
import type { CanisterId } from "$lib/types/canister";
import { AccountIdentifier, SubAccount } from "@dfinity/ledger-icp";
import { Principal } from "@dfinity/principal";
import { nonNullish } from "@dfinity/utils";
import { nonNullish, principalToSubAccount } from "@dfinity/utils";
import { get } from "svelte/store";
import { formatNumber } from "./format.utils";
import { replacePlaceholders } from "./i18n.utils";
Expand Down Expand Up @@ -98,3 +100,18 @@ export const areEnoughCyclesSelected = ({
amountCycles: number | undefined;
}): boolean =>
(amountCycles ?? 0) >= (minimumCycles ?? 0) && (amountCycles ?? 0) > 0;

export const getCanisterCreationCmcAccountIdentifierHex = ({
controller,
}: {
controller: Principal;
}): string => {
const subAccountBytes = principalToSubAccount(controller);
// To create a canister you need to send ICP to an account owned by the CMC, so that the CMC can burn those funds and generate cycles.
// To ensure everyone uses a unique address, the intended controller of the new canister is used to calculate the subaccount.
const accountId = AccountIdentifier.fromPrincipal({
principal: CYCLES_MINTING_CANISTER_ID,
subAccount: SubAccount.fromBytes(subAccountBytes) as SubAccount,
});
return accountId.toHex();
};
3 changes: 0 additions & 3 deletions frontend/src/routes/(app)/(nns)/tokens/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@
await uncertifiedLoadSnsesAccountsBalances({
rootCanisterIds: notLoadedCanisterIds.map((id) => Principal.fromText(id)),
excludeRootCanisterIds: [],
});
};
Expand Down Expand Up @@ -146,7 +145,6 @@
await uncertifiedLoadAccountsBalance({
universeIds,
excludeUniverseIds: [],
});
};
Expand All @@ -157,7 +155,6 @@
if (isSnsProject) {
return uncertifiedLoadSnsesAccountsBalances({
rootCanisterIds: [universeId],
excludeRootCanisterIds: [],
});
}
return loadAccountsBalances([universeId.toText()]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { mockFullNeuron, mockNeuron } from "$tests/mocks/neurons.mock";
import { LosingRewardNeuronsModalPo } from "$tests/page-objects/LosingRewardNeuronsModal.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
import { runResolvedPromises } from "$tests/utils/timers.test-utils";
import type { NeuronInfo } from "@dfinity/nns";
import { nonNullish } from "@dfinity/utils";
import { render } from "@testing-library/svelte";
import { get } from "svelte/store";
Expand Down Expand Up @@ -64,8 +65,13 @@ describe("LosingRewardNeuronsModal", () => {
}));
let spyRefreshVotingPower;

const renderComponent = ({ onClose }: { onClose?: () => void } = {}) => {
const { container, component } = render(LosingRewardNeuronsModal);
const renderComponent = ({
onClose,
neurons,
}: { onClose?: () => void; neurons?: NeuronInfo[] } = {}) => {
const { container, component } = render(LosingRewardNeuronsModal, {
props: { neurons },
});

if (nonNullish(onClose)) {
component.$on("nnsClose", onClose);
Expand Down Expand Up @@ -97,31 +103,11 @@ describe("LosingRewardNeuronsModal", () => {
.mockResolvedValue();
});

it("should not display active neurons", async () => {
neuronsStore.setNeurons({
neurons,
certified: true,
});
const po = await renderComponent();
const cards = await po.getNnsLosingRewardsNeuronCardPos();

expect(cards.length).toEqual(2);
expect(await cards[0].getNeuronId()).toEqual(
`${losingRewardsNeuron.neuronId}`
);
expect(await cards[1].getNeuronId()).toEqual(
`${in10DaysLosingRewardsNeuron.neuronId}`
);
});

it("should dispatch on close", async () => {
neuronsStore.setNeurons({
neurons,
certified: true,
});
const onClose = vi.fn();
const po = await renderComponent({
onClose,
neurons,
});

expect(onClose).toHaveBeenCalledTimes(0);
Expand All @@ -130,17 +116,19 @@ describe("LosingRewardNeuronsModal", () => {
});

it("should confirm following", async () => {
const neurons = [in10DaysLosingRewardsNeuron, losingRewardsNeuron];
neuronsStore.setNeurons({
neurons: [in10DaysLosingRewardsNeuron, losingRewardsNeuron],
neurons,
certified: true,
});
const po = await renderComponent({});
const po = await renderComponent({
neurons,
});

expect((await po.getNnsLosingRewardsNeuronCardPos()).length).toEqual(2);

await po.clickConfirmFollowing();
await runResolvedPromises();
expect((await po.getNnsLosingRewardsNeuronCardPos()).length).toEqual(0);

expect(spyRefreshVotingPower).toHaveBeenCalledTimes(2);
expect(spyRefreshVotingPower).toHaveBeenCalledWith({
Expand All @@ -154,13 +142,10 @@ describe("LosingRewardNeuronsModal", () => {
});

it("should navigate to the neuron details", async () => {
neuronsStore.setNeurons({
neurons: [losingRewardsNeuron],
certified: true,
});
const onClose = vi.fn();
const po = await renderComponent({
onClose,
neurons: [losingRewardsNeuron],
});
const firstCards = (await po.getNnsLosingRewardsNeuronCardPos())[0];
expect(onClose).toHaveBeenCalledTimes(0);
Expand All @@ -186,13 +171,11 @@ describe("LosingRewardNeuronsModal", () => {
const queryKnownNeuronsSpy = vi
.spyOn(governanceApi, "queryKnownNeurons")
.mockResolvedValue([]);
neuronsStore.setNeurons({
neurons,
certified: true,
});

expect(queryKnownNeuronsSpy).toHaveBeenCalledTimes(0);
await renderComponent();
await renderComponent({
neurons,
});
await runResolvedPromises();
expect(queryKnownNeuronsSpy).toHaveBeenCalledTimes(2);
expect(queryKnownNeuronsSpy).toHaveBeenCalledWith({
Expand Down
Loading

0 comments on commit 4d4b4c7

Please sign in to comment.