Skip to content

Commit

Permalink
SECFIND-244: Extract getCanisterCreationCmcAccountIdentifierHex from …
Browse files Browse the repository at this point in the history
…createCanister (#6112)

# Motivation

When creating a canister using the CMC, ICP are first transferred to a
specific account of the CMC.
We want to find such transaction to recover from interrupt canister
creation.
See [this
doc](https://docs.google.com/document/d/1hjMSTzjnVbU9Q4rJk233M3uNcKC-RLhme25nahHpTZg/edit?tab=t.0#heading=h.qtiuv2x3gsjy)
for details.

In this PR we just extract a utility function to calculate the CMC
account identifier.

# Changes

1. Extract `getCanisterCreationCmcAccountIdentifierHex` from
`createCanister`.
2. Use `getCanisterCreationCmcAccountIdentifierHex` in `createCanister`.

# Tests

Unit test added.

# Todos

- [ ] Add entry to changelog (if necessary).
not yet

---------

Co-authored-by: Yusef Habib <[email protected]>
Co-authored-by: Carly Gundy <[email protected]>
  • Loading branch information
3 people authored Jan 7, 2025
1 parent 39efc04 commit 43a1a9e
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 8 deletions.
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
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();
};
22 changes: 22 additions & 0 deletions frontend/src/tests/lib/utils/canisters.utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
canisterStatusToText,
errorCanisterNameMessage,
formatCyclesToTCycles,
getCanisterCreationCmcAccountIdentifierHex,
getCanisterFromStore,
isController,
isUserController,
Expand Down Expand Up @@ -267,4 +268,25 @@ describe("canister-utils", () => {
).toBeTruthy();
});
});

describe("getCanisterCreationCmcAccountIdentifierHex", () => {
it("should return the account identifier", () => {
const controller = Principal.fromText(
"efwjn-odjlf-7q4oi-62p6e-55cgt-opqxz-hwp7t-bp3d3-c2ykh-qrwth-6ae"
);
// The account identifier is created through hashing. It's not really
// useful to do the same hashing here in the test so we just test a
// hardcoded value.
// This value can be recreated as follows:
// CMC_CANISTER_ID="rkp4c-7iaaa-aaaaa-aaaca-cai"
// CONTROLLER="efwjn-odjlf-7q4oi-62p6e-55cgt-opqxz-hwp7t-bp3d3-c2ykh-qrwth-6ae"
// scripts/convert-id --input text --subaccount_format text --output account_identifier "$CMC_CANISTER_ID" "$CONTROLLER"
const expectedAccountId =
"c13de767ead7f7bfa4522847eab1385532e19ff1e79419c34f3999e1ca9be9a1";

expect(getCanisterCreationCmcAccountIdentifierHex({ controller })).toBe(
expectedAccountId
);
});
});
});

0 comments on commit 43a1a9e

Please sign in to comment.