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