junobuild_shared/mgmt/
ic.rs

1use crate::errors::{
2    JUNO_ERROR_CANISTER_CREATE_FAILED, JUNO_ERROR_CANISTER_INSTALL_CODE_FAILED,
3    JUNO_ERROR_CYCLES_DEPOSIT_BALANCE_LOW, JUNO_ERROR_CYCLES_DEPOSIT_FAILED,
4    JUNO_ERROR_SEGMENT_DELETE_FAILED, JUNO_ERROR_SEGMENT_STOP_FAILED,
5};
6use crate::mgmt::settings::{create_canister_cycles, create_canister_settings};
7use crate::mgmt::types::ic::{CreateCanisterInitSettingsArg, WasmArg};
8use crate::types::interface::DepositCyclesArgs;
9use candid::Principal;
10use ic_cdk::api::canister_cycle_balance;
11use ic_cdk::call::CallResult;
12use ic_cdk::management_canister::{
13    create_canister_with_extra_cycles, delete_canister, deposit_cycles as ic_deposit_cycles,
14    install_code as ic_install_code, stop_canister, update_settings, CanisterId,
15    CanisterInstallMode, CanisterSettings, CreateCanisterArgs, DeleteCanisterArgs,
16    DepositCyclesArgs as MgmtDepositCyclesArgs, InstallCodeArgs, StopCanisterArgs,
17    UpdateSettingsArgs,
18};
19
20/// Asynchronously creates a new canister and installs provided Wasm code with additional cycles.
21///
22/// # Arguments
23/// - `create_settings_arg`: The custom settings to apply to spinup the canister.
24/// - `wasm_arg`: Wasm binary and arguments to install in the new canister (`WasmArg` struct).
25/// - `cycles`: Additional cycles to deposit during canister creation on top of `CREATE_CANISTER_CYCLES`.
26///
27/// # Returns
28/// - `Ok(Principal)`: On success, returns the `Principal` ID of the newly created canister.
29/// - `Err(String)`: On failure, returns an error message.
30pub async fn create_and_install_canister_with_ic_mgmt(
31    create_settings_arg: &CreateCanisterInitSettingsArg,
32    wasm_arg: &WasmArg,
33    cycles: u128,
34) -> Result<Principal, String> {
35    let canister_id = create_canister_with_ic_mgmt(create_settings_arg, cycles).await?;
36
37    install_code(canister_id, wasm_arg, CanisterInstallMode::Install)
38        .await
39        .map_err(|_| JUNO_ERROR_CANISTER_INSTALL_CODE_FAILED.to_string())?;
40
41    Ok(canister_id)
42}
43
44/// Creates a new canister using the IC management canister.
45///
46/// # Arguments
47/// - `create_settings_arg`: Initial settings for the canister (controllers, compute allocation, etc.)
48/// - `cycles`: The number of cycles to deposit into the new canister
49///
50/// # Returns
51/// - `Ok(Principal)`: On success, returns the Principal ID of the newly created canister
52/// - `Err(String)`: On failure, returns an error message describing what went wrong
53///
54/// # Errors
55/// - Management canister call failures (network issues, invalid arguments, etc.)
56/// - Canister creation failures (insufficient cycles, etc.)
57pub async fn create_canister_with_ic_mgmt(
58    create_settings_arg: &CreateCanisterInitSettingsArg,
59    cycles: u128,
60) -> Result<Principal, String> {
61    let create_result = create_canister_with_extra_cycles(
62        &CreateCanisterArgs {
63            settings: create_canister_settings(create_settings_arg),
64        },
65        create_canister_cycles(cycles),
66    )
67    .await;
68
69    let result = create_result.map_err(|err| {
70        format!(
71            "{} ({})",
72            JUNO_ERROR_CANISTER_CREATE_FAILED,
73            &err.to_string()
74        )
75    })?;
76
77    Ok(result.canister_id)
78}
79
80/// Asynchronously installs code on a specified canister.
81///
82/// # Arguments
83/// - `canister_id`: `Principal` ID of the target canister.
84/// - `wasm_arg`: Contains the Wasm module and installation arguments.
85/// - `mode`: Installation mode defined by `CanisterInstallMode`.
86///
87/// # Returns
88/// - A `CallResult<()>` indicating success or failure.
89pub async fn install_code(
90    canister_id: Principal,
91    WasmArg { wasm, install_arg }: &WasmArg,
92    mode: CanisterInstallMode,
93) -> CallResult<()> {
94    let arg = InstallCodeArgs {
95        mode,
96        canister_id,
97        wasm_module: wasm.clone(),
98        arg: install_arg.clone(),
99    };
100
101    ic_install_code(&arg).await
102}
103
104/// Asynchronously updates the controller list of a specified canister.
105///
106/// # Arguments
107/// - `canister_id`: `Principal` ID of the target canister.
108/// - `controllers`: New list of `Principal` IDs to set as controllers.
109///
110/// # Returns
111/// - A `CallResult<()>` indicating success or failure.
112pub async fn update_canister_controllers(
113    canister_id: Principal,
114    controllers: Vec<Principal>,
115) -> CallResult<()> {
116    // Not including a setting in the settings record means not changing that field.
117    // In other words, setting wasm_memory_limit to None here means keeping the actual value of wasm_memory_limit.
118    let arg = UpdateSettingsArgs {
119        canister_id,
120        settings: CanisterSettings {
121            controllers: Some(controllers),
122            compute_allocation: None,
123            memory_allocation: None,
124            freezing_threshold: None,
125            reserved_cycles_limit: None,
126            log_visibility: None,
127            wasm_memory_limit: None,
128            wasm_memory_threshold: None,
129            environment_variables: None,
130        },
131    };
132
133    update_settings(&arg).await
134}
135
136/// Deposits cycles into a specified canister from the calling canister's balance.
137///
138/// # Arguments
139/// - `args`: `DepositCyclesArgs` struct containing the destination canister ID and cycle amount.
140///
141/// # Returns
142/// - `Ok(())`: On successful deposit.
143/// - `Err(String)`: If the balance is insufficient or on failure to deposit.
144pub async fn deposit_cycles(
145    DepositCyclesArgs {
146        destination_id,
147        cycles,
148    }: DepositCyclesArgs,
149) -> Result<(), String> {
150    let balance = canister_cycle_balance();
151
152    if balance < cycles {
153        return Err(format!(
154            "{JUNO_ERROR_CYCLES_DEPOSIT_BALANCE_LOW} (balance {balance}, {cycles} to deposit)"
155        ));
156    }
157
158    let result = ic_deposit_cycles(
159        &MgmtDepositCyclesArgs {
160            canister_id: destination_id,
161        },
162        cycles,
163    )
164    .await;
165
166    match result {
167        Err(err) => Err(format!(
168            "{} ({})",
169            JUNO_ERROR_CYCLES_DEPOSIT_FAILED,
170            &err.to_string()
171        )),
172        Ok(_) => Ok(()),
173    }
174}
175
176/// Stops the execution of a specified segment (canister).
177///
178/// # Arguments
179/// - `canister_id`: The `CanisterId` of the canister to stop.
180///
181/// # Returns
182/// - `Ok(())`: If the canister is successfully stopped.
183/// - `Err(String)`: On failure, returns an error message.
184pub async fn stop_segment(canister_id: CanisterId) -> Result<(), String> {
185    let result = stop_canister(&StopCanisterArgs { canister_id }).await;
186
187    match result {
188        Err(err) => Err(format!(
189            "{} ({})",
190            JUNO_ERROR_SEGMENT_STOP_FAILED,
191            &err.to_string()
192        )),
193        Ok(_) => Ok(()),
194    }
195}
196
197/// Deletes a specified segment (canister).
198///
199/// # Arguments
200/// - `canister_id`: The `CanisterId` of the canister to delete.
201///
202/// # Returns
203/// - `Ok(())`: If the canister is successfully deleted.
204/// - `Err(String)`: On failure, returns an error message.
205pub async fn delete_segment(canister_id: CanisterId) -> Result<(), String> {
206    let result = delete_canister(&DeleteCanisterArgs { canister_id }).await;
207
208    match result {
209        Err(err) => Err(format!(
210            "{} ({})",
211            JUNO_ERROR_SEGMENT_DELETE_FAILED,
212            &err.to_string()
213        )),
214        Ok(_) => Ok(()),
215    }
216}