junobuild_shared/mgmt/
ic.rs

1use crate::mgmt::settings::{create_canister_cycles, create_canister_settings};
2use crate::mgmt::types::ic::WasmArg;
3use crate::types::interface::DepositCyclesArgs;
4use candid::Principal;
5use ic_cdk::api::call::CallResult;
6use ic_cdk::api::canister_balance128;
7use ic_cdk::api::management_canister::main::{
8    create_canister, delete_canister, deposit_cycles as ic_deposit_cycles,
9    install_code as ic_install_code, stop_canister, update_settings, CanisterId, CanisterIdRecord,
10    CanisterInstallMode, CanisterSettings, CreateCanisterArgument, InstallCodeArgument,
11    UpdateSettingsArgument,
12};
13
14/// Asynchronously creates a new canister and installs provided Wasm code with additional cycles.
15///
16/// # Arguments
17/// - `controllers`: A list of `Principal` IDs to set as controllers of the new canister.
18/// - `wasm_arg`: Wasm binary and arguments to install in the new canister (`WasmArg` struct).
19/// - `cycles`: Additional cycles to deposit during canister creation on top of `CREATE_CANISTER_CYCLES`.
20///
21/// # Returns
22/// - `Ok(Principal)`: On success, returns the `Principal` ID of the newly created canister.
23/// - `Err(String)`: On failure, returns an error message.
24pub async fn create_canister_install_code(
25    controllers: Vec<Principal>,
26    wasm_arg: &WasmArg,
27    cycles: u128,
28) -> Result<Principal, String> {
29    let record = create_canister(
30        CreateCanisterArgument {
31            settings: create_canister_settings(controllers),
32        },
33        create_canister_cycles(cycles),
34    )
35    .await;
36
37    match record {
38        Err((_, message)) => Err(["Failed to create canister.", &message].join(" - ")),
39        Ok(record) => {
40            let canister_id = record.0.canister_id;
41
42            let install = install_code(canister_id, wasm_arg, CanisterInstallMode::Install).await;
43
44            match install {
45                Err(_) => Err("Failed to install code in canister.".to_string()),
46                Ok(_) => Ok(canister_id),
47            }
48        }
49    }
50}
51
52/// Asynchronously installs code on a specified canister.
53///
54/// # Arguments
55/// - `canister_id`: `Principal` ID of the target canister.
56/// - `wasm_arg`: Contains the Wasm module and installation arguments.
57/// - `mode`: Installation mode defined by `CanisterInstallMode`.
58///
59/// # Returns
60/// - A `CallResult<()>` indicating success or failure.
61pub async fn install_code(
62    canister_id: Principal,
63    WasmArg { wasm, install_arg }: &WasmArg,
64    mode: CanisterInstallMode,
65) -> CallResult<()> {
66    let arg = InstallCodeArgument {
67        mode,
68        canister_id,
69        wasm_module: wasm.clone(),
70        arg: install_arg.clone(),
71    };
72
73    ic_install_code(arg).await
74}
75
76/// Asynchronously updates the controller list of a specified canister.
77///
78/// # Arguments
79/// - `canister_id`: `Principal` ID of the target canister.
80/// - `controllers`: New list of `Principal` IDs to set as controllers.
81///
82/// # Returns
83/// - A `CallResult<()>` indicating success or failure.
84pub async fn update_canister_controllers(
85    canister_id: Principal,
86    controllers: Vec<Principal>,
87) -> CallResult<()> {
88    // Not including a setting in the settings record means not changing that field.
89    // In other words, setting wasm_memory_limit to None here means keeping the actual value of wasm_memory_limit.
90    let arg = UpdateSettingsArgument {
91        canister_id,
92        settings: CanisterSettings {
93            controllers: Some(controllers),
94            compute_allocation: None,
95            memory_allocation: None,
96            freezing_threshold: None,
97            reserved_cycles_limit: None,
98            log_visibility: None,
99            wasm_memory_limit: None,
100        },
101    };
102
103    update_settings(arg).await
104}
105
106/// Deposits cycles into a specified canister from the calling canister's balance.
107///
108/// # Arguments
109/// - `args`: `DepositCyclesArgs` struct containing the destination canister ID and cycle amount.
110///
111/// # Returns
112/// - `Ok(())`: On successful deposit.
113/// - `Err(String)`: If the balance is insufficient or on failure to deposit.
114pub async fn deposit_cycles(
115    DepositCyclesArgs {
116        destination_id,
117        cycles,
118    }: DepositCyclesArgs,
119) -> Result<(), String> {
120    let balance = canister_balance128();
121
122    if balance < cycles {
123        return Err(format!(
124            "Balance ({}) is lower than the amount of cycles {} to deposit.",
125            balance, cycles
126        ));
127    }
128
129    let result = ic_deposit_cycles(
130        CanisterIdRecord {
131            canister_id: destination_id,
132        },
133        cycles,
134    )
135    .await;
136
137    match result {
138        Err((_, message)) => Err(["Deposit cycles failed.", &message].join(" - ")),
139        Ok(_) => Ok(()),
140    }
141}
142
143/// Stops the execution of a specified segment (canister).
144///
145/// # Arguments
146/// - `canister_id`: The `CanisterId` of the canister to stop.
147///
148/// # Returns
149/// - `Ok(())`: If the canister is successfully stopped.
150/// - `Err(String)`: On failure, returns an error message.
151pub async fn stop_segment(canister_id: CanisterId) -> Result<(), String> {
152    let result = stop_canister(CanisterIdRecord { canister_id }).await;
153
154    match result {
155        Err((_, message)) => Err(["Cannot stop segment.", &message].join(" - ")),
156        Ok(_) => Ok(()),
157    }
158}
159
160/// Deletes a specified segment (canister).
161///
162/// # Arguments
163/// - `canister_id`: The `CanisterId` of the canister to delete.
164///
165/// # Returns
166/// - `Ok(())`: If the canister is successfully deleted.
167/// - `Err(String)`: On failure, returns an error message.
168pub async fn delete_segment(canister_id: CanisterId) -> Result<(), String> {
169    let result = delete_canister(CanisterIdRecord { canister_id }).await;
170
171    match result {
172        Err((_, message)) => Err(["Cannot delete segment.", &message].join(" - ")),
173        Ok(_) => Ok(()),
174    }
175}