diff --git a/e2e-tests/Cargo.toml b/e2e-tests/Cargo.toml index dbc57a5d9..aeeeed9de 100644 --- a/e2e-tests/Cargo.toml +++ b/e2e-tests/Cargo.toml @@ -12,7 +12,7 @@ publish = false candid.workspace = true cargo_metadata = "0.18" escargot = { version = "0.5.7", features = ["print"] } -ic-cdk.workspace = true +ic-cdk = { workspace = true, features = ["transform-closure"] } ic-cdk-timers.workspace = true lazy_static = "1.4.0" serde_bytes.workspace = true diff --git a/e2e-tests/src/bin/canister_info.rs b/e2e-tests/src/bin/canister_info.rs index 9a4bc2078..0ba383e18 100644 --- a/e2e-tests/src/bin/canister_info.rs +++ b/e2e-tests/src/bin/canister_info.rs @@ -1,70 +1,64 @@ use candid::Principal; -use ic_cdk::api::management_canister::main::{ +use ic_cdk::management_canister::{ canister_info, create_canister, install_code, uninstall_code, update_settings, - CanisterIdRecord, CanisterInfoRequest, CanisterInfoResponse, + CanisterInfoArgs, CanisterInfoResult, CanisterInstallMode::{Install, Reinstall, Upgrade}, - CanisterSettings, CreateCanisterArgument, InstallCodeArgument, UpdateSettingsArgument, + CanisterSettings, CreateCanisterArgs, InstallCodeArgs, UninstallCodeArgs, UpdateSettingsArgs, }; #[ic_cdk::update] -async fn info(canister_id: Principal) -> CanisterInfoResponse { - let request = CanisterInfoRequest { +async fn info(canister_id: Principal) -> CanisterInfoResult { + let request = CanisterInfoArgs { canister_id, num_requested_changes: Some(20), }; - canister_info(request).await.unwrap().0 + canister_info(request).await.unwrap() } #[ic_cdk::update] async fn canister_lifecycle() -> Principal { - let canister_id = create_canister(CreateCanisterArgument { settings: None }, 1_000_000_000_000) + let canister_id = create_canister(CreateCanisterArgs { settings: None }, 1_000_000_000_000) .await .unwrap() - .0; - install_code(InstallCodeArgument { + .canister_id; + install_code(InstallCodeArgs { mode: Install, arg: vec![], wasm_module: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], - canister_id: canister_id.canister_id, - }) - .await - .unwrap(); - uninstall_code(CanisterIdRecord { - canister_id: canister_id.canister_id, + canister_id, }) .await .unwrap(); - install_code(InstallCodeArgument { + uninstall_code(UninstallCodeArgs { canister_id }) + .await + .unwrap(); + install_code(InstallCodeArgs { mode: Install, arg: vec![], wasm_module: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], - canister_id: canister_id.canister_id, + canister_id, }) .await .unwrap(); - install_code(InstallCodeArgument { + install_code(InstallCodeArgs { mode: Reinstall, arg: vec![], wasm_module: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], - canister_id: canister_id.canister_id, + canister_id, }) .await .unwrap(); - install_code(InstallCodeArgument { + install_code(InstallCodeArgs { mode: Upgrade(None), arg: vec![], wasm_module: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], - canister_id: canister_id.canister_id, + canister_id, }) .await .unwrap(); - update_settings(UpdateSettingsArgument { + update_settings(UpdateSettingsArgs { settings: CanisterSettings { - controllers: Some(vec![ - ic_cdk::id(), - canister_id.canister_id, - Principal::anonymous(), - ]), + controllers: Some(vec![ic_cdk::id(), canister_id, Principal::anonymous()]), compute_allocation: None, memory_allocation: None, freezing_threshold: None, @@ -72,11 +66,11 @@ async fn canister_lifecycle() -> Principal { log_visibility: None, wasm_memory_limit: None, }, - canister_id: canister_id.canister_id, + canister_id, }) .await .unwrap(); - canister_id.canister_id + canister_id } fn main() {} diff --git a/e2e-tests/src/bin/chunk.rs b/e2e-tests/src/bin/chunk.rs index 9f387a6bd..2380b0ead 100644 --- a/e2e-tests/src/bin/chunk.rs +++ b/e2e-tests/src/bin/chunk.rs @@ -1,42 +1,41 @@ use candid::Principal; -use ic_cdk::api::management_canister::main::{ +use ic_cdk::management_canister::{ clear_chunk_store, create_canister, install_chunked_code, stored_chunks, upload_chunk, - CanisterInstallMode, ChunkHash, ClearChunkStoreArgument, CreateCanisterArgument, - InstallChunkedCodeArgument, StoredChunksArgument, UploadChunkArgument, + CanisterInstallMode, ChunkHash, ClearChunkStoreArgs, CreateCanisterArgs, + InstallChunkedCodeArgs, StoredChunksArgs, UploadChunkArgs, }; use ic_cdk::update; #[update] async fn call_create_canister() -> Principal { - let arg = CreateCanisterArgument::default(); + let arg = CreateCanisterArgs::default(); create_canister(arg, 1_000_000_000_000u128) .await .unwrap() - .0 .canister_id } #[update] async fn call_upload_chunk(canister_id: Principal, chunk: Vec) -> Vec { - let arg = UploadChunkArgument { + let arg = UploadChunkArgs { canister_id, chunk: chunk.to_vec(), }; - upload_chunk(arg).await.unwrap().0.hash + upload_chunk(arg).await.unwrap().hash } #[update] async fn call_stored_chunks(canister_id: Principal) -> Vec> { - let arg = StoredChunksArgument { canister_id }; - let hashes = stored_chunks(arg).await.unwrap().0; + let arg = StoredChunksArgs { canister_id }; + let hashes = stored_chunks(arg).await.unwrap(); hashes.into_iter().map(|v| v.hash).collect() } #[update] async fn call_clear_chunk_store(canister_id: Principal) { - let arg = ClearChunkStoreArgument { canister_id }; + let arg = ClearChunkStoreArgs { canister_id }; clear_chunk_store(arg).await.unwrap(); } @@ -50,7 +49,7 @@ async fn call_install_chunked_code( .iter() .map(|v| ChunkHash { hash: v.clone() }) .collect(); - let arg = InstallChunkedCodeArgument { + let arg = InstallChunkedCodeArgs { mode: CanisterInstallMode::Install, target_canister: canister_id, store_canister: None, diff --git a/e2e-tests/src/bin/http_request.rs b/e2e-tests/src/bin/http_request.rs new file mode 100644 index 000000000..fa738adb6 --- /dev/null +++ b/e2e-tests/src/bin/http_request.rs @@ -0,0 +1,152 @@ +use ic_cdk::management_canister::{ + http_request, http_request_with_closure, HttpHeader, HttpMethod, HttpRequestArgs, + HttpRequestResult, TransformArgs, TransformContext, +}; +use ic_cdk::{query, update}; + +/// The formula to calculate the cost of a request. +fn cycles_cost(args: &HttpRequestArgs) -> u128 { + const N: u128 = 13; + let request_bytes_len = (args.url.len() + + args + .headers + .iter() + .map(|h| h.name.len() + h.value.len()) + .sum::() + + args.body.as_ref().map(|b| b.len()).unwrap_or(0) + + args + .transform + .as_ref() + .map(|t| t.context.len() + t.function.0.method.len()) + .unwrap_or(0)) as u128; + let response_bytes_len = args.max_response_bytes.unwrap_or(2_000_000) as u128; + (3_000_000 + 60_000 * N) * N + 400 * N * request_bytes_len + 800 * N * response_bytes_len +} + +/// All fields are Some except transform. +#[update] +async fn get_without_transform() { + let args = HttpRequestArgs { + url: "https://example.com".to_string(), + method: HttpMethod::GET, + headers: vec![HttpHeader { + name: "request_header_name".to_string(), + value: "request_header_value".to_string(), + }], + body: Some(vec![1]), + max_response_bytes: Some(100_000), + transform: None, + }; + let cycles = cycles_cost(&args); + let res = http_request(args, cycles).await.unwrap(); + assert_eq!(res.status, 200u32); + assert_eq!( + res.headers, + vec![HttpHeader { + name: "response_header_name".to_string(), + value: "response_header_value".to_string(), + }] + ); + assert_eq!(res.body, vec![42]); +} + +/// Method is POST. +#[update] +async fn post() { + let args = HttpRequestArgs { + url: "https://example.com".to_string(), + method: HttpMethod::POST, + ..Default::default() + }; + let cycles = cycles_cost(&args); + http_request(args, cycles).await.unwrap(); +} + +/// Method is HEAD. +#[update] +async fn head() { + let args = HttpRequestArgs { + url: "https://example.com".to_string(), + method: HttpMethod::HEAD, + ..Default::default() + }; + let cycles = cycles_cost(&args); + http_request(args, cycles).await.unwrap(); +} + +/// The standard way to define a transform function. +/// +/// It is a query endpoint that takes a TransformArgs and returns an HttpRequestResult. +#[query] +fn transform(args: TransformArgs) -> HttpRequestResult { + let mut body = args.response.body; + body.push(args.context[0]); + HttpRequestResult { + status: args.response.status, + headers: args.response.headers, + body, + } +} + +/// Set the transform field with the name of the transform query endpoint. +#[update] +async fn get_with_transform() { + let args = HttpRequestArgs { + url: "https://example.com".to_string(), + method: HttpMethod::GET, + transform: Some(TransformContext::from_name( + "transform".to_string(), + vec![42], + )), + ..Default::default() + }; + let cycles = cycles_cost(&args); + let res = http_request(args, cycles).await.unwrap(); + assert_eq!(res.status, 200u32); + assert_eq!( + res.headers, + vec![HttpHeader { + name: "response_header_name".to_string(), + value: "response_header_value".to_string(), + }] + ); + // The first 42 is from the response body, the second 42 is from the transform context. + assert_eq!(res.body, vec![42, 42]); +} + +/// Set the transform field with a closure. +#[update] +async fn get_with_transform_closure() { + let transform = |args: HttpRequestResult| { + let mut body = args.body; + body.push(42); + HttpRequestResult { + status: args.status, + headers: args.headers, + body, + } + }; + let args = HttpRequestArgs { + url: "https://example.com".to_string(), + method: HttpMethod::GET, + transform: None, + ..Default::default() + }; + // The transform closure takes 40 bytes. + let cycles = cycles_cost(&args) + 40 * 400 * 13; + let res = http_request_with_closure(args.clone(), cycles, transform) + .await + .unwrap(); + assert_eq!(res.status, 200u32); + assert_eq!( + res.headers, + vec![HttpHeader { + name: "response_header_name".to_string(), + value: "response_header_value".to_string(), + }] + ); + // The first 42 is from the response body, the second 42 is from the transform closure. + assert_eq!(res.body, vec![42, 42]); +} + +fn main() {} diff --git a/e2e-tests/src/bin/management_caller.rs b/e2e-tests/src/bin/management_caller.rs deleted file mode 100644 index c0d0d6089..000000000 --- a/e2e-tests/src/bin/management_caller.rs +++ /dev/null @@ -1,187 +0,0 @@ -use ic_cdk::prelude::*; - -/// Some management canister "main" methods are tested with other e2e canisters: -/// - canister_info.rs -/// - chunk.rs -mod main { - use super::*; - use candid::Principal; - use ic_cdk::api::management_canister::main::*; - #[update] - async fn execute_main_methods() { - let arg = CreateCanisterArgument { - settings: Some(CanisterSettings { - controllers: Some(vec![ic_cdk::id()]), - // There is no canister in the subnet, so we can set it to 100. - compute_allocation: Some(1u8.into()), - // Though the upper limit is 256TiB, the actual subnet may have less memory resource (e.g. local replica). - // Here we set it to 10KiB for testing. - memory_allocation: Some(10000u16.into()), - freezing_threshold: Some(u64::MAX.into()), - reserved_cycles_limit: Some(u128::MAX.into()), - log_visibility: Some(LogVisibility::Public), - wasm_memory_limit: Some((2u64.pow(48) - 1).into()), - }), - }; - let canister_id = create_canister(arg, 200_000_000_000_000_000_000_000_000u128) - .await - .unwrap() - .0 - .canister_id; - - let canister_id_record = CanisterIdRecord { canister_id }; - let response = canister_status(canister_id_record).await.unwrap().0; - assert_eq!(response.status, CanisterStatusType::Running); - assert_eq!(response.reserved_cycles.0, 0u128.into()); - let definite_canister_setting = response.settings; - assert_eq!(definite_canister_setting.controllers, vec![ic_cdk::id()]); - assert_eq!(definite_canister_setting.compute_allocation, 1u8); - assert_eq!(definite_canister_setting.memory_allocation, 10000u16); - assert_eq!(definite_canister_setting.freezing_threshold, u64::MAX); - assert_eq!(definite_canister_setting.reserved_cycles_limit, u128::MAX); - assert_eq!( - definite_canister_setting.log_visibility, - LogVisibility::Public - ); - assert_eq!( - definite_canister_setting.wasm_memory_limit, - 2u64.pow(48) - 1 - ); - - let arg = UpdateSettingsArgument { - canister_id, - settings: CanisterSettings::default(), - }; - update_settings(arg).await.unwrap(); - - let arg = InstallCodeArgument { - mode: CanisterInstallMode::Install, - canister_id, - // A minimal valid wasm module - // wat2wasm "(module)" - wasm_module: b"\x00asm\x01\x00\x00\x00".to_vec(), - arg: vec![], - }; - install_code(arg).await.unwrap(); - - uninstall_code(canister_id_record).await.unwrap(); - start_canister(canister_id_record).await.unwrap(); - stop_canister(canister_id_record).await.unwrap(); - deposit_cycles(canister_id_record, 1_000_000_000_000u128) - .await - .unwrap(); - delete_canister(canister_id_record).await.unwrap(); - let response = raw_rand().await.unwrap().0; - assert_eq!(response.len(), 32); - } - - #[update] - async fn execute_subnet_info(subnet_id: Principal) { - let arg = SubnetInfoArgs { subnet_id }; - let response = subnet_info(arg).await.unwrap().0; - assert!(!response.replica_version.is_empty()); - } -} - -mod provisional { - use super::*; - use ic_cdk::api::management_canister::{main::LogVisibility, provisional::*}; - - #[update] - async fn execute_provisional_methods() { - let settings = CanisterSettings { - controllers: Some(vec![ic_cdk::caller()]), - compute_allocation: Some(50u8.into()), - memory_allocation: Some(10000u16.into()), - freezing_threshold: Some(10000u16.into()), - reserved_cycles_limit: Some(10000u16.into()), - log_visibility: Some(LogVisibility::Public), - wasm_memory_limit: Some(10000u16.into()), - }; - let arg = ProvisionalCreateCanisterWithCyclesArgument { - amount: Some(10_000_000_000_000u64.into()), - settings: Some(settings), - }; - let canister_id = provisional_create_canister_with_cycles(arg) - .await - .unwrap() - .0 - .canister_id; - - let arg = ProvisionalTopUpCanisterArgument { - canister_id, - amount: 1_000_000_000u64.into(), - }; - provisional_top_up_canister(arg).await.unwrap(); - } -} - -mod snapshot { - use super::*; - use ic_cdk::api::management_canister::main::*; - - #[update] - async fn execute_snapshot_methods() { - let arg = CreateCanisterArgument::default(); - let canister_id = create_canister(arg, 2_000_000_000_000u128) - .await - .unwrap() - .0 - .canister_id; - - // Cannot take a snapshot of a canister that is empty. - // So we install a minimal wasm module. - let arg = InstallCodeArgument { - mode: CanisterInstallMode::Install, - canister_id, - // A minimal valid wasm module - // wat2wasm "(module)" - wasm_module: b"\x00asm\x01\x00\x00\x00".to_vec(), - arg: vec![], - }; - install_code(arg).await.unwrap(); - - let arg = TakeCanisterSnapshotArgs { - canister_id, - replace_snapshot: None, - }; - let snapshot = take_canister_snapshot(arg).await.unwrap().0; - - let arg = LoadCanisterSnapshotArgs { - canister_id, - snapshot_id: snapshot.id.clone(), - sender_canister_version: None, - }; - assert!(load_canister_snapshot(arg).await.is_ok()); - - let canister_id_record = CanisterIdRecord { canister_id }; - let snapshots = list_canister_snapshots(canister_id_record).await.unwrap().0; - assert_eq!(snapshots.len(), 1); - assert_eq!(snapshots[0].id, snapshot.id); - - let arg = DeleteCanisterSnapshotArgs { - canister_id, - snapshot_id: snapshot.id.clone(), - }; - assert!(delete_canister_snapshot(arg).await.is_ok()); - - let arg = CanisterInfoRequest { - canister_id, - num_requested_changes: Some(1), - }; - let canister_info_response = canister_info(arg).await.unwrap().0; - assert_eq!(canister_info_response.total_num_changes, 3); - assert_eq!(canister_info_response.recent_changes.len(), 1); - if let CanisterChange { - details: CanisterChangeDetails::LoadSnapshot(load_snapshot_record), - .. - } = &canister_info_response.recent_changes[0] - { - assert_eq!(load_snapshot_record.snapshot_id, snapshot.id); - } else { - panic!("Expected the most recent change to be LoadSnapshot"); - } - } -} - -fn main() {} diff --git a/e2e-tests/src/bin/management_canister.rs b/e2e-tests/src/bin/management_canister.rs new file mode 100644 index 000000000..8e25925b5 --- /dev/null +++ b/e2e-tests/src/bin/management_canister.rs @@ -0,0 +1,280 @@ +use candid::Principal; +use ic_cdk::api::canister_self; +use ic_cdk::management_canister::*; +use ic_cdk::update; +use sha2::Digest; + +#[update] +async fn basic() { + // create_canister + let self_id = canister_self(); + let arg = CreateCanisterArgs { + settings: Some(CanisterSettings { + controllers: Some(vec![self_id]), + compute_allocation: Some(0u8.into()), + memory_allocation: Some(0u8.into()), + freezing_threshold: Some(0u8.into()), + reserved_cycles_limit: Some(0u8.into()), + log_visibility: Some(LogVisibility::Public), + wasm_memory_limit: Some(0u8.into()), + }), + }; + // 500 B is the minimum cycles required to create a canister. + // Here we set 1 T cycles for other operations below. + let canister_id = create_canister(arg, 1_000_000_000_000u128) + .await + .unwrap() + .canister_id; + + // canister_status + let arg = CanisterStatusArgs { canister_id }; + let result = canister_status(arg).await.unwrap(); + assert_eq!(result.status, CanisterStatusType::Running); + assert_eq!(result.reserved_cycles.0, 0u128.into()); + let definite_canister_setting = result.settings; + assert_eq!(definite_canister_setting.controllers, vec![self_id]); + assert_eq!(definite_canister_setting.compute_allocation, 0u8); + assert_eq!(definite_canister_setting.memory_allocation, 0u8); + assert_eq!(definite_canister_setting.freezing_threshold, 0u8); + assert_eq!(definite_canister_setting.reserved_cycles_limit, 0u8); + assert_eq!( + definite_canister_setting.log_visibility, + LogVisibility::Public + ); + assert_eq!(definite_canister_setting.wasm_memory_limit, 0u8); + + // update_settings + let arg = UpdateSettingsArgs { + canister_id, + settings: CanisterSettings { + freezing_threshold: Some(0u16.into()), + log_visibility: Some(LogVisibility::AllowedViewers(vec![self_id])), + ..Default::default() + }, + }; + update_settings(arg).await.unwrap(); + + // install_code + let arg = InstallCodeArgs { + mode: CanisterInstallMode::Install, + canister_id, + // A minimal valid wasm module + // wat2wasm "(module)" + wasm_module: b"\x00asm\x01\x00\x00\x00".to_vec(), + arg: vec![], + }; + install_code(arg).await.unwrap(); + + // uninstall_code + let arg = UninstallCodeArgs { canister_id }; + uninstall_code(arg).await.unwrap(); + + // start_canister + let arg = StartCanisterArgs { canister_id }; + start_canister(arg).await.unwrap(); + + // stop_canister + let arg = StopCanisterArgs { canister_id }; + stop_canister(arg).await.unwrap(); + + // deposit_cycles + let arg = DepositCyclesArgs { canister_id }; + deposit_cycles(arg, 1_000_000_000_000u128).await.unwrap(); + + // delete_canister + let arg = DeleteCanisterArgs { canister_id }; + delete_canister(arg).await.unwrap(); + + // raw_rand + let bytes = raw_rand().await.unwrap(); + assert_eq!(bytes.len(), 32); +} + +#[update] +async fn ecdsa() { + // ecdsa_public_key + let key_id = EcdsaKeyId { + curve: EcdsaCurve::Secp256k1, + name: "test_key_1".to_string(), + }; + let derivation_path = vec![]; + let arg = EcdsaPublicKeyArgs { + canister_id: None, + derivation_path: derivation_path.clone(), + key_id: key_id.clone(), + }; + let EcdsaPublicKeyResult { + public_key, + chain_code, + } = ecdsa_public_key(arg).await.unwrap(); + assert_eq!(public_key.len(), 33); + assert_eq!(chain_code.len(), 32); + + let message = "hello world"; + let message_hash = sha2::Sha256::digest(message).to_vec(); + let arg = SignWithEcdsaArgs { + message_hash, + derivation_path, + key_id, + }; + let SignWithEcdsaResult { signature } = sign_with_ecdsa(arg).await.unwrap(); + assert_eq!(signature.len(), 64); +} + +#[update] +async fn schnorr() { + // schnorr_public_key + let key_id = SchnorrKeyId { + algorithm: SchnorrAlgorithm::Bip340secp256k1, + name: "test_key_1".to_string(), + }; + let derivation_path = vec![]; + let arg = SchnorrPublicKeyArgs { + canister_id: None, + derivation_path: derivation_path.clone(), + key_id: key_id.clone(), + }; + let SchnorrPublicKeyResult { + public_key, + chain_code, + } = schnorr_public_key(arg).await.unwrap(); + assert_eq!(public_key.len(), 33); + assert_eq!(chain_code.len(), 32); + let arg = SchnorrPublicKeyArgs { + canister_id: None, + derivation_path: derivation_path.clone(), + key_id: SchnorrKeyId { + algorithm: SchnorrAlgorithm::Ed25519, + name: "test_key_1".to_string(), + }, + }; + let SchnorrPublicKeyResult { + public_key, + chain_code, + } = schnorr_public_key(arg).await.unwrap(); + assert_eq!(public_key.len(), 32); + assert_eq!(chain_code.len(), 32); + + // sign_with_schnorr + let message = "hello world".into(); + let arg = SignWithSchnorrArgs { + message, + derivation_path, + key_id, + }; + let SignWithSchnorrResult { signature } = sign_with_schnorr(arg).await.unwrap(); + assert_eq!(signature.len(), 64); +} + +#[update] +async fn metrics(subnet_id: Principal) { + // node_metrics_history + let arg = NodeMetricsHistoryArgs { + subnet_id, + start_at_timestamp_nanos: 0, + }; + let result = node_metrics_history(arg).await.unwrap(); + for record in result { + assert!(record.timestamp_nanos > 0); + assert!(!record.node_metrics.is_empty()); + } +} + +#[update] +async fn subnet(subnet_id: Principal) { + // subnet_info + let arg = SubnetInfoArgs { subnet_id }; + let result = subnet_info(arg).await.unwrap(); + assert!(!result.replica_version.is_empty()); +} + +#[update] +async fn provisional() { + // provisional_create_canister_with_cycles + let settings = CanisterSettings { + log_visibility: Some(LogVisibility::Controllers), + ..Default::default() + }; + let arg = ProvisionalCreateCanisterWithCyclesArgs { + amount: Some(10_000_000_000_000u64.into()), + settings: Some(settings), + }; + let canister_id = provisional_create_canister_with_cycles(arg) + .await + .unwrap() + .canister_id; + + // provisional_top_up_canister + let arg = ProvisionalTopUpCanisterArgs { + canister_id, + amount: 1_000_000_000u64.into(), + }; + provisional_top_up_canister(arg).await.unwrap(); +} + +#[update] +async fn snapshots() { + let arg = CreateCanisterArgs::default(); + let canister_id = create_canister(arg, 2_000_000_000_000u128) + .await + .unwrap() + .canister_id; + + // Cannot take a snapshot of a canister that is empty. + // So we install a minimal wasm module. + let arg = InstallCodeArgs { + mode: CanisterInstallMode::Install, + canister_id, + // A minimal valid wasm module + // wat2wasm "(module)" + wasm_module: b"\x00asm\x01\x00\x00\x00".to_vec(), + arg: vec![], + }; + install_code(arg).await.unwrap(); + + // take_canister_snapshot + let arg = TakeCanisterSnapshotArgs { + canister_id, + replace_snapshot: None, + }; + let snapshot = take_canister_snapshot(arg).await.unwrap(); + + // load_canister_snapshot + let arg = LoadCanisterSnapshotArgs { + canister_id, + snapshot_id: snapshot.id.clone(), + }; + assert!(load_canister_snapshot(arg).await.is_ok()); + + // list_canister_snapshots + let args = ListCanisterSnapshotsArgs { canister_id }; + let snapshots = list_canister_snapshots(args).await.unwrap(); + assert_eq!(snapshots.len(), 1); + assert_eq!(snapshots[0].id, snapshot.id); + + // delete_canister_snapshot + let arg = DeleteCanisterSnapshotArgs { + canister_id, + snapshot_id: snapshot.id.clone(), + }; + assert!(delete_canister_snapshot(arg).await.is_ok()); + + // check the above snapshot operations are recorded in the canister's history. + let arg = CanisterInfoArgs { + canister_id, + num_requested_changes: Some(1), + }; + let canister_info_result = canister_info(arg).await.unwrap(); + assert_eq!(canister_info_result.total_num_changes, 3); + assert_eq!(canister_info_result.recent_changes.len(), 1); + if let CanisterChange { + details: CanisterChangeDetails::LoadSnapshot(load_snapshot_record), + .. + } = &canister_info_result.recent_changes[0] + { + assert_eq!(load_snapshot_record.snapshot_id, snapshot.id); + } else { + panic!("Expected the most recent change to be LoadSnapshot"); + } +} +fn main() {} diff --git a/e2e-tests/tests/canister_info.rs b/e2e-tests/tests/canister_info.rs index 827ee0689..c0ffcce03 100644 --- a/e2e-tests/tests/canister_info.rs +++ b/e2e-tests/tests/canister_info.rs @@ -1,19 +1,22 @@ use candid::Principal; -use ic_cdk::api::management_canister::main::{ - CanisterChange, CanisterChangeDetails, CanisterChangeOrigin, CanisterIdRecord, - CanisterInfoResponse, CanisterInstallMode, +use ic_cdk::management_canister::{ + CanisterChange, CanisterChangeDetails, CanisterChangeOrigin, CanisterInfoResult, + CanisterInstallMode, CodeDeploymentMode::{Install, Reinstall, Upgrade}, CodeDeploymentRecord, ControllersChangeRecord, CreationRecord, FromCanisterRecord, - FromUserRecord, InstallCodeArgument, + FromUserRecord, InstallCodeArgs, UninstallCodeArgs, }; use ic_cdk_e2e_tests::cargo_build_canister; use pocket_ic::common::rest::RawEffectivePrincipal; -use pocket_ic::{call_candid, call_candid_as, PocketIc}; +use pocket_ic::{call_candid, call_candid_as, PocketIcBuilder}; use std::time::UNIX_EPOCH; #[test] fn test_canister_info() { - let pic = PocketIc::new(); + let pic = PocketIcBuilder::new() + .with_application_subnet() + .with_nonmainnet_features(true) + .build(); let wasm = cargo_build_canister("canister_info"); // As of PocketIC server v5.0.0 and client v4.0.0, the first canister creation happens at (time0+4). // Each operation advances the Pic by 2 nanos, except for the last operation which advances only by 1 nano. @@ -43,7 +46,7 @@ fn test_canister_info() { RawEffectivePrincipal::None, Principal::anonymous(), "uninstall_code", - (CanisterIdRecord { + (UninstallCodeArgs { canister_id: new_canister.0, },), ) @@ -54,7 +57,7 @@ fn test_canister_info() { RawEffectivePrincipal::None, Principal::anonymous(), "install_code", - (InstallCodeArgument { + (InstallCodeArgs { mode: CanisterInstallMode::Install, arg: vec![], wasm_module: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], @@ -63,7 +66,7 @@ fn test_canister_info() { ) .expect("Error calling install_code"); - let info: (CanisterInfoResponse,) = call_candid( + let info: (CanisterInfoResult,) = call_candid( &pic, canister_id, RawEffectivePrincipal::None, @@ -74,7 +77,7 @@ fn test_canister_info() { assert_eq!( info.0, - CanisterInfoResponse { + CanisterInfoResult { total_num_changes: 9, recent_changes: vec![ CanisterChange { diff --git a/e2e-tests/tests/chunk.rs b/e2e-tests/tests/chunk.rs index f52835961..b3b09086e 100644 --- a/e2e-tests/tests/chunk.rs +++ b/e2e-tests/tests/chunk.rs @@ -1,12 +1,15 @@ use candid::Principal; use ic_cdk_e2e_tests::cargo_build_canister; use pocket_ic::common::rest::RawEffectivePrincipal; -use pocket_ic::{call_candid, PocketIc}; +use pocket_ic::{call_candid, PocketIcBuilder}; use sha2::Digest; #[test] fn test_chunk() { - let pic = PocketIc::new(); + let pic = PocketIcBuilder::new() + .with_application_subnet() + .with_nonmainnet_features(true) + .build(); let wasm = cargo_build_canister("chunk"); let canister_id = pic.create_canister(); pic.add_cycles(canister_id, 100_000_000_000_000); @@ -18,7 +21,7 @@ fn test_chunk() { "call_create_canister", (), ) - .expect("Error calling call_create_canister"); + .unwrap(); let wasm_module = b"\x00asm\x01\x00\x00\x00".to_vec(); let wasm_module_hash = sha2::Sha256::digest(&wasm_module).to_vec(); @@ -34,7 +37,7 @@ fn test_chunk() { "call_upload_chunk", (target_canister_id, chunk1.clone()), ) - .expect("Error calling call_upload_chunk"); + .unwrap(); assert_eq!(&hash1_return, &hash1_expected); let () = call_candid( @@ -44,7 +47,7 @@ fn test_chunk() { "call_clear_chunk_store", (target_canister_id,), ) - .expect("Error calling call_clear_chunk_store"); + .unwrap(); let (_hash1_return,): (Vec,) = call_candid( &pic, @@ -53,7 +56,7 @@ fn test_chunk() { "call_upload_chunk", (target_canister_id, chunk1), ) - .expect("Error calling call_upload_chunk"); + .unwrap(); let (_hash2_return,): (Vec,) = call_candid( &pic, canister_id, @@ -61,7 +64,7 @@ fn test_chunk() { "call_upload_chunk", (target_canister_id, chunk2), ) - .expect("Error calling call_upload_chunk"); + .unwrap(); let (hashes,): (Vec>,) = call_candid( &pic, @@ -70,7 +73,7 @@ fn test_chunk() { "call_stored_chunks", (target_canister_id,), ) - .expect("Error calling call_stored_chunks"); + .unwrap(); // the hashes returned are not guaranteed to be in order assert_eq!(hashes.len(), 2); assert!(hashes.contains(&hash1_expected)); @@ -88,5 +91,5 @@ fn test_chunk() { wasm_module_hash, ), ) - .expect("Error calling call_install_chunked_code"); + .unwrap(); } diff --git a/e2e-tests/tests/http_request.rs b/e2e-tests/tests/http_request.rs new file mode 100644 index 000000000..8c18e68e2 --- /dev/null +++ b/e2e-tests/tests/http_request.rs @@ -0,0 +1,66 @@ +use candid::{Encode, Principal}; +use ic_cdk_e2e_tests::cargo_build_canister; +use pocket_ic::common::rest::{ + CanisterHttpHeader, CanisterHttpReply, CanisterHttpRequest, CanisterHttpResponse, + MockCanisterHttpResponse, +}; +use pocket_ic::{PocketIc, PocketIcBuilder, WasmResult}; + +#[test] +fn test_http_request() { + let pic = PocketIcBuilder::new() + .with_application_subnet() + .with_nonmainnet_features(true) + .build(); + + let wasm = cargo_build_canister("http_request"); + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, 3_000_000_000_000u128); + pic.install_canister(canister_id, wasm, vec![], None); + + test_one_http_request(&pic, canister_id, "get_without_transform"); + test_one_http_request(&pic, canister_id, "post"); + test_one_http_request(&pic, canister_id, "head"); + test_one_http_request(&pic, canister_id, "get_with_transform"); + test_one_http_request(&pic, canister_id, "get_with_transform_closure"); +} + +fn test_one_http_request(pic: &PocketIc, canister_id: Principal, method: &str) { + let call_id = pic + .submit_call( + canister_id, + Principal::anonymous(), + method, + Encode!(&()).unwrap(), + ) + .unwrap(); + let canister_http_requests = tick_until_next_request(pic); + assert_eq!(canister_http_requests.len(), 1); + let request = &canister_http_requests[0]; + pic.mock_canister_http_response(MockCanisterHttpResponse { + subnet_id: request.subnet_id, + request_id: request.request_id, + response: CanisterHttpResponse::CanisterHttpReply(CanisterHttpReply { + status: 200, + headers: vec![CanisterHttpHeader { + name: "response_header_name".to_string(), + value: "response_header_value".to_string(), + }], + body: vec![42], + }), + additional_responses: vec![], + }); + let result = pic.await_call(call_id).unwrap(); + assert!(matches!(result, WasmResult::Reply(_))); +} + +fn tick_until_next_request(pic: &PocketIc) -> Vec { + for _ in 0..10 { + let requests = pic.get_canister_http(); + if !requests.is_empty() { + return requests; + } + pic.tick(); + } + vec![] +} diff --git a/e2e-tests/tests/management_caller.rs b/e2e-tests/tests/management_caller.rs deleted file mode 100644 index f9863b792..000000000 --- a/e2e-tests/tests/management_caller.rs +++ /dev/null @@ -1,49 +0,0 @@ -use ic_cdk_e2e_tests::cargo_build_canister; -use pocket_ic::common::rest::RawEffectivePrincipal; -use pocket_ic::PocketIcBuilder; -use pocket_ic::{call_candid, PocketIc}; - -#[test] -fn test_call_management() { - let pic = PocketIc::new(); - let wasm = cargo_build_canister("management_caller"); - let canister_id = pic.create_canister(); - pic.add_cycles(canister_id, 300_000_000_000_000_000_000_000_000u128); - pic.install_canister(canister_id, wasm, vec![], None); - let () = call_candid( - &pic, - canister_id, - RawEffectivePrincipal::None, - "execute_main_methods", - (), - ) - .expect("Error calling execute_main_methods"); - let () = call_candid( - &pic, - canister_id, - RawEffectivePrincipal::None, - "execute_provisional_methods", - (), - ) - .expect("Error calling execute_provisional_methods"); -} - -#[test] -fn test_snapshot() { - let pic = PocketIcBuilder::new() - .with_application_subnet() - .with_nonmainnet_features(true) - .build(); - let wasm = cargo_build_canister("management_caller"); - let canister_id = pic.create_canister(); - pic.add_cycles(canister_id, 300_000_000_000_000_000_000_000_000u128); - pic.install_canister(canister_id, wasm, vec![], None); - let () = call_candid( - &pic, - canister_id, - RawEffectivePrincipal::None, - "execute_snapshot_methods", - (), - ) - .expect("Error calling execute_snapshot_methods"); -} diff --git a/e2e-tests/tests/management_canister.rs b/e2e-tests/tests/management_canister.rs new file mode 100644 index 000000000..aff0726bd --- /dev/null +++ b/e2e-tests/tests/management_canister.rs @@ -0,0 +1,61 @@ +use ic_cdk_e2e_tests::cargo_build_canister; +use pocket_ic::call_candid; +use pocket_ic::common::rest::RawEffectivePrincipal; +use pocket_ic::PocketIcBuilder; + +#[test] +fn test_management_canister() { + let pic = PocketIcBuilder::new() + .with_application_subnet() + .with_nonmainnet_features(true) + .with_ii_subnet() // Required for ecdsa and schnorr + .build(); + + let wasm = cargo_build_canister("management_canister"); + let canister_id = pic.create_canister(); + let subnet_id = pic.get_subnet(canister_id).unwrap(); + pic.add_cycles(canister_id, 10_000_000_000_000u128); // 10 T + pic.install_canister(canister_id, wasm, vec![], None); + let () = call_candid(&pic, canister_id, RawEffectivePrincipal::None, "basic", ()).unwrap(); + let () = call_candid(&pic, canister_id, RawEffectivePrincipal::None, "ecdsa", ()).unwrap(); + let () = call_candid( + &pic, + canister_id, + RawEffectivePrincipal::None, + "schnorr", + (), + ) + .unwrap(); + let () = call_candid( + &pic, + canister_id, + RawEffectivePrincipal::None, + "metrics", + (subnet_id,), + ) + .unwrap(); + let () = call_candid( + &pic, + canister_id, + RawEffectivePrincipal::None, + "subnet", + (subnet_id,), + ) + .unwrap(); + let () = call_candid( + &pic, + canister_id, + RawEffectivePrincipal::None, + "provisional", + (), + ) + .unwrap(); + let () = call_candid( + &pic, + canister_id, + RawEffectivePrincipal::None, + "snapshots", + (), + ) + .unwrap(); +} diff --git a/ic-cdk/src/api/management_canister/http_request/mod.rs b/ic-cdk/src/api/management_canister/http_request/mod.rs index 48e214d2e..acbb883ab 100644 --- a/ic-cdk/src/api/management_canister/http_request/mod.rs +++ b/ic-cdk/src/api/management_canister/http_request/mod.rs @@ -32,11 +32,11 @@ pub async fn http_request( #[cfg(feature = "transform-closure")] thread_local! { #[allow(clippy::type_complexity)] - static TRANSFORMS: RefCell HttpResponse>>> = RefCell::default(); + static TRANSFORMS_LEGACY: RefCell HttpResponse>>> = RefCell::default(); } #[cfg(feature = "transform-closure")] -#[export_name = "canister_query http_transform"] +#[export_name = "canister_query http_transform_legacy"] extern "C" fn http_transform() { use crate::api::{ call::{arg_data, reply, ArgDecoderConfig}, @@ -49,7 +49,7 @@ extern "C" fn http_transform() { let (args,): (TransformArgs,) = arg_data(ArgDecoderConfig::default()); let int = u64::from_be_bytes(args.context[..].try_into().unwrap()); let key = DefaultKey::from(KeyData::from_ffi(int)); - let func = TRANSFORMS.with(|transforms| transforms.borrow_mut().remove(key)); + let func = TRANSFORMS_LEGACY.with(|transforms| transforms.borrow_mut().remove(key)); let Some(func) = func else { crate::trap(&format!("Missing transform function for request {int}")); }; @@ -77,11 +77,11 @@ pub async fn http_request_with_closure( "`CanisterHttpRequestArgument`'s `transform` field must be `None` when using a closure" ); let transform_func = Box::new(transform_func) as _; - let key = TRANSFORMS.with(|transforms| transforms.borrow_mut().insert(transform_func)); + let key = TRANSFORMS_LEGACY.with(|transforms| transforms.borrow_mut().insert(transform_func)); struct DropGuard(DefaultKey); impl Drop for DropGuard { fn drop(&mut self) { - TRANSFORMS.with(|transforms| transforms.borrow_mut().remove(self.0)); + TRANSFORMS_LEGACY.with(|transforms| transforms.borrow_mut().remove(self.0)); } } let key = DropGuard(key); @@ -89,7 +89,7 @@ pub async fn http_request_with_closure( let arg = CanisterHttpRequestArgument { transform: Some(TransformContext { function: TransformFunc(candid::Func { - method: " http_transform".into(), + method: " http_transform_legacy".into(), principal: crate::id(), }), context, diff --git a/ic-cdk/src/api/mod.rs b/ic-cdk/src/api/mod.rs index 0ed9663ee..f54c185ae 100644 --- a/ic-cdk/src/api/mod.rs +++ b/ic-cdk/src/api/mod.rs @@ -54,6 +54,18 @@ pub fn id() -> Principal { Principal::try_from(&bytes).unwrap() } +/// Get the canister ID of the current canister. +pub fn canister_self() -> Principal { + // SAFETY: ic0.canister_self_size is always safe to call. + let len = unsafe { ic0::canister_self_size() }; + let mut bytes = vec![0u8; len]; + // SAFETY: Because `bytes` is mutable, and allocated to `len` bytes, it is safe to be passed to `ic0.canister_self_copy` with a 0-offset. + unsafe { + ic0::canister_self_copy(bytes.as_mut_ptr() as usize, 0, len); + } + Principal::try_from(&bytes).unwrap() +} + /// Gets the amount of funds available in the canister. pub fn canister_balance128() -> u128 { let mut recv = 0u128; diff --git a/ic-cdk/src/lib.rs b/ic-cdk/src/lib.rs index e02faded4..beeb8dfd7 100644 --- a/ic-cdk/src/lib.rs +++ b/ic-cdk/src/lib.rs @@ -18,6 +18,7 @@ pub mod api; pub mod call; mod futures; mod macros; +pub mod management_canister; mod printer; pub mod storage; diff --git a/ic-cdk/src/management_canister.rs b/ic-cdk/src/management_canister.rs new file mode 100644 index 000000000..0fe50662b --- /dev/null +++ b/ic-cdk/src/management_canister.rs @@ -0,0 +1,1741 @@ +//! Functions and types for calling [the IC management canister][1]. +//! +//! This module is a direct translation from its Candid interface description. +//! +//! The functions and types defined in this module serves these purposes: +//! * Make it easy to construct correct request data. +//! * Handle the response ergonomically. +//! * For those calls require cycles payments, the cycles amount is an explicit argument. +//! +//! [1]: https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-management-canister + +use crate::api::canister_version; +use crate::call::{Call, CallResult, ConfigurableCall, SendableCall}; +use candid::{CandidType, Nat, Principal}; +use serde::{Deserialize, Serialize}; + +/// Canister ID. +pub type CanisterId = Principal; + +/// Chunk hash. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct ChunkHash { + /// The hash of an uploaded chunk + #[serde(with = "serde_bytes")] + pub hash: Vec, +} + +/// Log Visibility. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub enum LogVisibility { + /// Controllers. + #[default] + #[serde(rename = "controllers")] + Controllers, + /// Public. + #[serde(rename = "public")] + Public, + /// Allowed viewers. + #[serde(rename = "allowed_viewers")] + AllowedViewers(Vec), +} + +/// Canister settings. +/// +/// The settings are optional. If they are not explicitly set, the default values will be applied automatically. +/// +/// See [`settings`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-create_canister). +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct CanisterSettings { + /// A list of at most 10 principals. + /// + /// The principals in this list become the *controllers* of the canister. + /// + /// Default value: A list containing only the caller of the create_canister call. + pub controllers: Option>, + /// Must be a number between 0 and 100, inclusively. + /// + /// It indicates how much compute power should be guaranteed to this canister, + /// expressed as a percentage of the maximum compute power that a single canister can allocate. + /// + /// If the IC cannot provide the requested allocation, + /// for example because it is oversubscribed, the call will be **rejected**. + /// + /// Default value: 0 + pub compute_allocation: Option, + /// Must be a number between 0 and 248 (i.e 256TB), inclusively. + /// + /// It indicates how much memory the canister is allowed to use in total. + /// + /// If the IC cannot provide the requested allocation, + /// for example because it is oversubscribed, the call will be **rejected**. + /// + /// If set to 0, then memory growth of the canister will be best-effort and subject to the available memory on the IC. + /// + /// Default value: 0 + pub memory_allocation: Option, + /// Must be a number between 0 and 264-1, inclusively. + /// + /// It indicates a length of time in seconds. + /// + /// Default value: 2592000 (approximately 30 days). + pub freezing_threshold: Option, + /// Must be a number between 0 and 2128-1, inclusively. + /// + /// It indicates the upper limit on `reserved_cycles` of the canister. + /// + /// Default value: 5_000_000_000_000 (5 trillion cycles). + pub reserved_cycles_limit: Option, + /// Defines who is allowed to read the canister's logs. + /// + /// Default value: Controllers + pub log_visibility: Option, + /// Must be a number between 0 and 248-1 (i.e 256TB), inclusively. + /// + /// It indicates the upper limit on the WASM heap memory consumption of the canister. + /// + /// Default value: 3_221_225_472 (3 GiB). + pub wasm_memory_limit: Option, +} + +/// Like [`CanisterSettings`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct DefiniteCanisterSettings { + /// Controllers of the canister. + pub controllers: Vec, + /// Compute allocation. + pub compute_allocation: Nat, + /// Memory allocation. + pub memory_allocation: Nat, + /// Freezing threshold. + pub freezing_threshold: Nat, + /// Reserved cycles limit. + pub reserved_cycles_limit: Nat, + /// Visibility of canister logs. + pub log_visibility: LogVisibility, + /// The Wasm memory limit. + pub wasm_memory_limit: Nat, +} + +// create_canister ------------------------------------------------------------ + +/// Register a new canister and get its canister id. +/// +/// See [IC method `create_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-create_canister). +/// +/// This call requires cycles payment. The required cycles varies according to the subnet size (number of nodes). +/// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost) for more details. +pub async fn create_canister( + arg: CreateCanisterArgs, + cycles: u128, +) -> CallResult { + let complete_arg = CreateCanisterArgsComplete { + settings: arg.settings, + sender_canister_version: Some(canister_version()), + }; + Call::new(Principal::management_canister(), "create_canister") + .with_arg(complete_arg) + .with_guaranteed_response() + .with_cycles(cycles) + .call() + .await +} + +/// Argument type of [`create_canister`]. +/// +/// Please note that this type is a reduced version of [`CreateCanisterArgsComplete`]. +/// The `sender_canister_version` field is removed as it is set automatically in [`create_canister`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct CreateCanisterArgs { + /// See [`CanisterSettings`]. + pub settings: Option, +} + +/// Complete argument type of `create_canister`. +/// +/// Please note that this type is not used directly as the argument of [`create_canister`]. +/// The function [`create_canister`] takes [`CreateCanisterArgs`] instead. +/// +/// If you want to manually call `create_canister` (construct and invoke a [`Call`]), you should use this complete type. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct CreateCanisterArgsComplete { + /// See [`CanisterSettings`]. + pub settings: Option, + /// sender_canister_version must be set to [`canister_version`]. + pub sender_canister_version: Option, +} + +/// Result type of [`create_canister`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, +)] +pub struct CreateCanisterResult { + /// Canister ID. + pub canister_id: CanisterId, +} + +// create_canister END -------------------------------------------------------- + +// update_settings ------------------------------------------------------------ + +/// Update the settings of a canister. +/// +/// See [IC method `update_settings`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-update_settings). +pub async fn update_settings(arg: UpdateSettingsArgs) -> CallResult<()> { + let complete_arg = UpdateSettingsArgsComplete { + canister_id: arg.canister_id, + settings: arg.settings, + sender_canister_version: Some(canister_version()), + }; + Call::new(Principal::management_canister(), "update_settings") + .with_arg(complete_arg) + .call() + .await +} + +/// Argument type of [`update_settings`] +/// +/// Please note that this type is a reduced version of [`UpdateSettingsArgsComplete`]. +/// +/// The `sender_canister_version` field is removed as it is set automatically in [`update_settings`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct UpdateSettingsArgs { + /// Canister ID. + pub canister_id: CanisterId, + /// See [CanisterSettings]. + pub settings: CanisterSettings, +} + +/// Complete argument type of `update_settings`. +/// +/// Please note that this type is not used directly as the argument of [`update_settings`]. +/// The function [`update_settings`] takes [`UpdateSettingsArgs`] instead. +/// +/// If you want to manually call `update_settings` (construct and invoke a [Call]), you should use this complete type. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct UpdateSettingsArgsComplete { + /// Canister ID. + pub canister_id: CanisterId, + /// See [CanisterSettings]. + pub settings: CanisterSettings, + /// sender_canister_version must be set to [`canister_version`]. + pub sender_canister_version: Option, +} + +// update_settings END -------------------------------------------------------- + +// upload_chunk --------------------------------------------------------------- + +/// Upload a chunk to the chunk store of a canister. +/// +/// See [IC method `upload_chunk`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-upload_chunk). +pub async fn upload_chunk(arg: UploadChunkArgs) -> CallResult { + Call::new(Principal::management_canister(), "upload_chunk") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`upload_chunk`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct UploadChunkArgs { + /// The canister whose chunk store the chunk will be uploaded to. + pub canister_id: CanisterId, + /// The chunk bytes (max size 1MB). + #[serde(with = "serde_bytes")] + pub chunk: Vec, +} + +/// Result type of [`upload_chunk`]. +pub type UploadChunkResult = ChunkHash; + +// upload_chunk END ----------------------------------------------------------- + +// clear_chunk_store ---------------------------------------------------------- + +/// Clear the chunk store of a canister. +/// +/// See [IC method `clear_chunk_store`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-clear_chunk_store). +pub async fn clear_chunk_store(arg: ClearChunkStoreArgs) -> CallResult<()> { + Call::new(Principal::management_canister(), "clear_chunk_store") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`clear_chunk_store`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct ClearChunkStoreArgs { + /// The canister whose chunk store will be cleared. + pub canister_id: CanisterId, +} + +// clear_chunk_store END ------------------------------------------------------ + +// stored_chunks -------------------------------------------------------------- + +/// Get the hashes of all chunks stored in the chunk store of a canister. +/// +/// See [IC method `stored_chunks`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-stored_chunks). +pub async fn stored_chunks(arg: StoredChunksArgs) -> CallResult { + Call::new(Principal::management_canister(), "stored_chunks") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`stored_chunks`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct StoredChunksArgs { + /// The canister whose chunk store will be queried. + pub canister_id: CanisterId, +} + +/// Result type of [`stored_chunks`]. +pub type StoredChunksResult = Vec; + +// stored_chunks END ---------------------------------------------------------- + +// install_code --------------------------------------------------------------- + +/// Install code into a canister. +/// +/// See [IC method `install_code`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-install_code). +pub async fn install_code(arg: InstallCodeArgs) -> CallResult<()> { + let complete_arg = InstallCodeArgsComplete { + mode: arg.mode, + canister_id: arg.canister_id, + wasm_module: arg.wasm_module, + arg: arg.arg, + sender_canister_version: Some(canister_version()), + }; + Call::new(Principal::management_canister(), "install_code") + .with_arg(complete_arg) + .call() + .await +} + +/// The mode with which a canister is installed. +/// +/// This second version of the mode allows someone to specify the +/// optional `SkipPreUpgrade` parameter in case of an upgrade +#[derive( + CandidType, + Serialize, + Deserialize, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Clone, + Copy, + Default, +)] +pub enum CanisterInstallMode { + /// A fresh install of a new canister. + #[default] + #[serde(rename = "install")] + Install, + /// Reinstalling a canister that was already installed. + #[serde(rename = "reinstall")] + Reinstall, + /// Upgrade an existing canister. + #[serde(rename = "upgrade")] + Upgrade(Option), +} + +/// Flags for canister installation with [`CanisterInstallMode::Upgrade`]. +#[derive( + CandidType, + Serialize, + Deserialize, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Clone, + Copy, + Default, +)] +pub struct UpgradeFlags { + /// If set to `true`, the `pre_upgrade` step will be skipped during the canister upgrade. + pub skip_pre_upgrade: Option, + /// If set to `Keep`, the WASM heap memory will be preserved instead of cleared. + pub wasm_memory_persistence: Option, +} + +/// Wasm memory persistence setting for [`UpgradeFlags`]. +#[derive( + CandidType, + Serialize, + Deserialize, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Clone, + Copy, + Default, +)] +pub enum WasmMemoryPersistence { + /// Preserve heap memory. + #[serde(rename = "keep")] + Keep, + /// Clear heap memory. + #[default] + #[serde(rename = "replace")] + Replace, +} + +/// WASM module. +pub type WasmModule = Vec; + +/// Argument type of [`install_code`]. +/// +/// Please note that this type is a reduced version of [`InstallCodeArgsComplete`]. +/// The `sender_canister_version` field is removed as it is set automatically in [`install_code`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct InstallCodeArgs { + /// See [CanisterInstallMode]. + pub mode: CanisterInstallMode, + /// Canister ID. + pub canister_id: CanisterId, + /// Code to be installed. + pub wasm_module: WasmModule, + /// The argument to be passed to `canister_init` or `canister_post_upgrade`. + #[serde(with = "serde_bytes")] + pub arg: Vec, +} + +/// Complete argument type of `install_code`. +/// +/// Please note that this type is not used directly as the argument of [`install_code`]. +/// The function [`install_code`] takes [`InstallCodeArgs`] instead. +/// +/// If you want to manually call `install_code` (construct and invoke a [`Call`]), you should use this complete type. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct InstallCodeArgsComplete { + /// See [`CanisterInstallMode`]. + pub mode: CanisterInstallMode, + /// Canister ID. + pub canister_id: CanisterId, + /// Code to be installed. + pub wasm_module: WasmModule, + /// The argument to be passed to `canister_init` or `canister_post_upgrade`. + #[serde(with = "serde_bytes")] + pub arg: Vec, + /// sender_canister_version must be set to [`canister_version`]. + pub sender_canister_version: Option, +} + +// install_code END ----------------------------------------------------------- + +// install_chunked_code ------------------------------------------------------- + +/// Install code into a canister where the code has previously been uploaded in chunks. +/// +/// See [IC method `install_chunked_code`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-install_chunked_code). +pub async fn install_chunked_code(arg: InstallChunkedCodeArgs) -> CallResult<()> { + let complete_arg = InstallChunkedCodeArgsComplete { + mode: arg.mode, + target_canister: arg.target_canister, + store_canister: arg.store_canister, + chunk_hashes_list: arg.chunk_hashes_list, + wasm_module_hash: arg.wasm_module_hash, + arg: arg.arg, + sender_canister_version: Some(canister_version()), + }; + Call::new(Principal::management_canister(), "install_chunked_code") + .with_arg(complete_arg) + .call() + .await +} + +/// Argument type of [`install_chunked_code`]. +/// +/// Please note that this type is a reduced version of [`InstallChunkedCodeArgsComplete`]. +/// The `sender_canister_version` field is removed as it is set automatically in [`install_chunked_code`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct InstallChunkedCodeArgs { + /// See [`CanisterInstallMode`]. + pub mode: CanisterInstallMode, + /// Principal of the canister being installed. + pub target_canister: CanisterId, + /// The canister in whose chunk storage the chunks are stored (defaults to target_canister if not specified). + pub store_canister: Option, + /// The list of chunks that make up the canister wasm. + pub chunk_hashes_list: Vec, + /// The sha256 hash of the wasm. + #[serde(with = "serde_bytes")] + pub wasm_module_hash: Vec, + /// The argument to be passed to `canister_init` or `canister_post_upgrade`. + #[serde(with = "serde_bytes")] + pub arg: Vec, +} + +/// Complete argument type of `install_chunked_code`. +/// +/// Please note that this type is not used directly as the argument of [`install_chunked_code`]. +/// The function [`install_chunked_code`] takes [`InstallChunkedCodeArgs`] instead. +/// +/// If you want to manually call `install_chunked_code` (construct and invoke a [`Call`]), you should use this complete type. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct InstallChunkedCodeArgsComplete { + /// See [`CanisterInstallMode`]. + pub mode: CanisterInstallMode, + /// Principal of the canister being installed. + pub target_canister: CanisterId, + /// The canister in whose chunk storage the chunks are stored (defaults to target_canister if not specified). + pub store_canister: Option, + /// The list of chunks that make up the canister wasm. + pub chunk_hashes_list: Vec, + /// The sha256 hash of the wasm. + #[serde(with = "serde_bytes")] + pub wasm_module_hash: Vec, + /// The argument to be passed to `canister_init` or `canister_post_upgrade`. + #[serde(with = "serde_bytes")] + pub arg: Vec, + /// sender_canister_version must be set to [`canister_version`]. + pub sender_canister_version: Option, +} + +// install_chunked_code END --------------------------------------------------- + +// uninstall_code ------------------------------------------------------------- + +/// Remove a canister's code and state, making the canister empty again. +/// +/// See [IC method `uninstall_code`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-uninstall_code). +pub async fn uninstall_code(arg: UninstallCodeArgs) -> CallResult<()> { + let complete_arg = UninstallCodeArgsComplete { + canister_id: arg.canister_id, + sender_canister_version: Some(canister_version()), + }; + Call::new(Principal::management_canister(), "uninstall_code") + .with_arg(complete_arg) + .call() + .await +} + +/// Argument type of [`uninstall_code`]. +/// +/// Please note that this type is a reduced version of [`UninstallCodeArgsComplete`]. +/// The `sender_canister_version` field is removed as it is set automatically in [`uninstall_code`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct UninstallCodeArgs { + /// Canister ID. + pub canister_id: CanisterId, +} + +/// Complete argument type of `uninstall_code`. +/// +/// Please note that this type is not used directly as the argument of [`uninstall_code`]. +/// The function [`uninstall_code`] takes [`UninstallCodeArgs`] instead. +/// +/// If you want to manually call `uninstall_code` (construct and invoke a [Call]), you should use this complete type. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct UninstallCodeArgsComplete { + /// Canister ID. + pub canister_id: CanisterId, + /// sender_canister_version must be set to [`canister_version`]. + pub sender_canister_version: Option, +} + +// uninstall_code END --------------------------------------------------------- + +// start_canister ------------------------------------------------------------- + +/// Start a canister if the canister status was `stopped` or `stopping`. +/// +/// See [IC method `start_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-start_canister). +pub async fn start_canister(arg: StartCanisterArgs) -> CallResult<()> { + Call::new(Principal::management_canister(), "start_canister") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`start_canister`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct StartCanisterArgs { + /// Canister ID. + pub canister_id: CanisterId, +} + +// start_canister END --------------------------------------------------------- + +// stop_canister -------------------------------------------------------------- + +/// Stop a canister. +/// +/// See [IC method `stop_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-stop_canister). +pub async fn stop_canister(arg: StopCanisterArgs) -> CallResult<()> { + Call::new(Principal::management_canister(), "stop_canister") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`stop_canister`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct StopCanisterArgs { + /// Canister ID. + pub canister_id: CanisterId, +} + +// stop_canister END ---------------------------------------------------------- + +// canister_status ------------------------------------------------------------ + +/// Get status information about the canister. +/// +/// See [IC method `canister_status`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-canister_status). +pub async fn canister_status(arg: CanisterStatusArgs) -> CallResult { + Call::new(Principal::management_canister(), "canister_status") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`canister_status`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct CanisterStatusArgs { + /// Canister ID. + pub canister_id: CanisterId, +} + +/// Return type of [`canister_status`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct CanisterStatusResult { + /// See [CanisterStatusType]. + pub status: CanisterStatusType, + /// See [DefiniteCanisterSettings]. + pub settings: DefiniteCanisterSettings, + /// A SHA256 hash of the module installed on the canister. This is null if the canister is empty. + pub module_hash: Option>, + /// The memory size taken by the canister. + pub memory_size: Nat, + /// The cycle balance of the canister. + pub cycles: Nat, + /// The reserved cycles balance of the canister. + /// These are cycles that are reserved by the resource reservation mechanism + /// on storage allocation. See also the `reserved_cycles_limit` parameter in + /// canister settings. + pub reserved_cycles: Nat, + /// Amount of cycles burned per day. + pub idle_cycles_burned_per_day: Nat, + /// Query statistics. + pub query_stats: QueryStats, +} + +/// Status of a canister. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, +)] +pub enum CanisterStatusType { + /// The canister is running. + #[serde(rename = "running")] + Running, + /// The canister is stopping. + #[serde(rename = "stopping")] + Stopping, + /// The canister is stopped. + #[serde(rename = "stopped")] + Stopped, +} + +/// Query statistics, returned by [`canister_status`]. +#[derive( + CandidType, Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, +)] +pub struct QueryStats { + /// Total number of query calls. + pub num_calls_total: Nat, + /// Total number of instructions executed by query calls. + pub num_instructions_total: Nat, + /// Total number of payload bytes use for query call requests. + pub request_payload_bytes_total: Nat, + /// Total number of payload bytes use for query call responses. + pub response_payload_bytes_total: Nat, +} + +// canister_status END -------------------------------------------------------- + +// canister_info -------------------------------------------------------------- + +/// Get public information about the canister. +/// +/// See [IC method `canister_info`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-canister_info). +pub async fn canister_info(arg: CanisterInfoArgs) -> CallResult { + Call::new(Principal::management_canister(), "canister_info") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`canister_info`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct CanisterInfoArgs { + /// Canister ID. + pub canister_id: Principal, + /// Number of most recent changes requested to be retrieved from canister history. + /// No changes are retrieved if this field is null. + pub num_requested_changes: Option, +} + +/// Return type of [`canister_info`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct CanisterInfoResult { + /// Total number of changes ever recorded in canister history. + /// This might be higher than the number of canister changes in `recent_changes` + /// because the IC might drop old canister changes from its history + /// (with `20` most recent canister changes to always remain in the list). + pub total_num_changes: u64, + /// The canister changes stored in the order from the oldest to the most recent. + pub recent_changes: Vec, + /// A SHA256 hash of the module installed on the canister. This is null if the canister is empty. + pub module_hash: Option>, + /// Controllers of the canister. + pub controllers: Vec, +} + +/// Details about a canister change initiated by a user. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct FromUserRecord { + /// Principal of the user. + pub user_id: Principal, +} + +/// Details about a canister change initiated by a canister (called _originator_). +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct FromCanisterRecord { + /// Canister ID of the originator. + pub canister_id: Principal, + /// Canister version of the originator when the originator initiated the change. + /// This is null if the original does not include its canister version + /// in the field `sender_canister_version` of the management canister payload. + pub canister_version: Option, +} + +/// Provides details on who initiated a canister change. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub enum CanisterChangeOrigin { + /// See [`FromUserRecord`]. + #[serde(rename = "from_user")] + FromUser(FromUserRecord), + /// See [`FromCanisterRecord`]. + #[serde(rename = "from_canister")] + FromCanister(FromCanisterRecord), +} + +/// Details about a canister creation. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct CreationRecord { + /// Initial set of canister controllers. + pub controllers: Vec, +} + +/// The mode with which a canister is installed. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, +)] +pub enum CodeDeploymentMode { + /// A fresh install of a new canister. + #[serde(rename = "install")] + Install, + /// Reinstalling a canister that was already installed. + #[serde(rename = "reinstall")] + Reinstall, + /// Upgrade an existing canister. + #[serde(rename = "upgrade")] + Upgrade, +} + +/// Details about a canister code deployment. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct CodeDeploymentRecord { + /// See [`CodeDeploymentMode`]. + pub mode: CodeDeploymentMode, + /// A SHA256 hash of the new module installed on the canister. + #[serde(with = "serde_bytes")] + pub module_hash: Vec, +} + +/// Details about loading canister snapshot. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct LoadSnapshotRecord { + /// The version of the canister at the time that the snapshot was taken + pub canister_version: u64, + /// The ID of the snapshot that was loaded. + pub snapshot_id: SnapshotId, + /// The timestamp at which the snapshot was taken. + pub taken_at_timestamp: u64, +} + +/// Details about updating canister controllers. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct ControllersChangeRecord { + /// The full new set of canister controllers. + pub controllers: Vec, +} + +/// Provides details on the respective canister change. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub enum CanisterChangeDetails { + /// See [`CreationRecord`]. + #[serde(rename = "creation")] + Creation(CreationRecord), + /// Uninstalling canister's module. + #[serde(rename = "code_uninstall")] + CodeUninstall, + /// See [`CodeDeploymentRecord`]. + #[serde(rename = "code_deployment")] + CodeDeployment(CodeDeploymentRecord), + /// See [`LoadSnapshotRecord`]. + #[serde(rename = "load_snapshot")] + LoadSnapshot(LoadSnapshotRecord), + /// See [`ControllersChangeRecord`]. + #[serde(rename = "controllers_change")] + ControllersChange(ControllersChangeRecord), +} + +/// Represents a canister change as stored in the canister history. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct CanisterChange { + /// The system timestamp (in nanoseconds since Unix Epoch) at which the change was performed. + pub timestamp_nanos: u64, + /// The canister version after performing the change. + pub canister_version: u64, + /// The change's origin (a user or a canister). + pub origin: CanisterChangeOrigin, + /// The change's details. + pub details: CanisterChangeDetails, +} + +// canister_info END ---------------------------------------------------------- + +// delete_canister ------------------------------------------------------------ + +/// Delete a canister. +/// +/// See [IC method `delete_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-delete_canister). +pub async fn delete_canister(arg: DeleteCanisterArgs) -> CallResult<()> { + Call::new(Principal::management_canister(), "delete_canister") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`delete_canister`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct DeleteCanisterArgs { + /// Canister ID. + pub canister_id: CanisterId, +} + +// delete_canister END -------------------------------------------------------- + +// deposit_cycles ------------------------------------------------------------- + +/// Deposit cycles to a canister. +/// +/// See [IC method `deposit_cycles`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-deposit_cycles). +pub async fn deposit_cycles(arg: DepositCyclesArgs, cycles: u128) -> CallResult<()> { + Call::new(Principal::management_canister(), "deposit_cycles") + .with_arg(arg) + .with_guaranteed_response() + .with_cycles(cycles) + .call() + .await +} + +/// Argument type of [`deposit_cycles`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct DepositCyclesArgs { + /// Canister ID. + pub canister_id: CanisterId, +} + +// deposit_cycles END --------------------------------------------------------- + +// raw_rand ------------------------------------------------------------------- + +// Get 32 pseudo-random bytes. +/// +/// See [IC method `raw_rand`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-raw_rand). +pub async fn raw_rand() -> CallResult> { + Call::new(Principal::management_canister(), "raw_rand") + .call() + .await +} + +// raw_rand END --------------------------------------------------------------- + +// http_request --------------------------------------------------------------- + +/// Make an HTTP request to a given URL and return the HTTP response, possibly after a transformation. +/// +/// See [IC method `http_request`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-http_request). +/// +/// This call requires cycles payment. The required cycles is a function of the request size and max_response_bytes. +/// Check [HTTPS outcalls cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost#https-outcalls) for more details. +pub async fn http_request(arg: HttpRequestArgs, cycles: u128) -> CallResult { + Call::new(Principal::management_canister(), "http_request") + .with_arg(arg) + .with_guaranteed_response() + .with_cycles(cycles) + .call() + .await +} + +/// Argument type of [`http_request`]. +#[derive(CandidType, Deserialize, Debug, PartialEq, Eq, Clone, Default)] +pub struct HttpRequestArgs { + /// The requested URL. + pub url: String, + /// The maximal size of the response in bytes. If None, 2MiB will be the limit. + /// This value affects the cost of the http request and it is highly recommended + /// to set it as low as possible to avoid unnecessary extra costs. + /// See also the [pricing section of HTTP outcalls documentation](https://internetcomputer.org/docs/current/developer-docs/integrations/http_requests/http_requests-how-it-works#pricing). + pub max_response_bytes: Option, + /// The method of HTTP request. + pub method: HttpMethod, + /// List of HTTP request headers and their corresponding values. + pub headers: Vec, + /// Optionally provide request body. + pub body: Option>, + /// Name of the transform function which is `func (transform_args) -> (http_response) query`. + /// Set to `None` if you are using `http_request_with` or `http_request_with_cycles_with`. + pub transform: Option, +} + +/// The returned HTTP response. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct HttpRequestResult { + /// The response status (e.g., 200, 404). + pub status: candid::Nat, + /// List of HTTP response headers and their corresponding values. + pub headers: Vec, + /// The response’s body. + #[serde(with = "serde_bytes")] + pub body: Vec, +} + +/// HTTP method. +#[derive( + CandidType, + Serialize, + Deserialize, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Clone, + Copy, + Default, +)] +pub enum HttpMethod { + /// GET + #[default] + #[serde(rename = "get")] + GET, + /// POST + #[serde(rename = "post")] + POST, + /// HEAD + #[serde(rename = "head")] + HEAD, +} +/// HTTP header. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct HttpHeader { + /// Name + pub name: String, + /// Value + pub value: String, +} + +/// ```text +/// record { +/// function : func(record { response : http_request_result; context : blob }) -> (http_request_result) query; +/// context : blob; +/// }; +/// ``` +#[derive(CandidType, Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct TransformContext { + /// `func(record { response : http_request_result; context : blob }) -> (http_request_result) query;`. + pub function: TransformFunc, + + /// Context to be passed to `transform` function to transform HTTP response for consensus + #[serde(with = "serde_bytes")] + pub context: Vec, +} + +impl TransformContext { + /// Constructs a [`TransformContext`] from a query method name and context. + /// The principal is assumed to be the ID of current canister. + pub fn from_name(candid_function_name: String, context: Vec) -> Self { + Self { + context, + function: TransformFunc(candid::Func { + method: candid_function_name, + principal: crate::api::canister_self(), + }), + } + } +} + +mod transform_func { + #![allow(missing_docs)] + use super::{HttpRequestResult, TransformArgs}; + candid::define_function!(pub TransformFunc : (TransformArgs) -> (HttpRequestResult) query); +} + +/// "transform" function of type: `func(record { response : http_request_result; context : blob }) -> (http_request_result) query` +pub use transform_func::TransformFunc; + +/// Type used for encoding/decoding: +/// `record { +/// response : http_response; +/// context : blob; +/// }` +#[derive(CandidType, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct TransformArgs { + /// Raw response from remote service, to be transformed + pub response: HttpRequestResult, + + /// Context for response transformation + #[serde(with = "serde_bytes")] + pub context: Vec, +} + +#[cfg(feature = "transform-closure")] +mod transform_closure { + use super::{ + http_request, CallResult, HttpRequestArgs, HttpRequestResult, Principal, TransformArgs, + TransformContext, + }; + use slotmap::{DefaultKey, Key, KeyData, SlotMap}; + use std::cell::RefCell; + + thread_local! { + #[allow(clippy::type_complexity)] + static TRANSFORMS: RefCell HttpRequestResult>>> = RefCell::default(); + } + + #[export_name = "canister_query http_transform"] + extern "C" fn http_transform() { + use crate::api::{ + call::{arg_data, reply, ArgDecoderConfig}, + caller, + }; + if caller() != Principal::management_canister() { + crate::trap("This function is internal to ic-cdk and should not be called externally."); + } + crate::setup(); + let (args,): (TransformArgs,) = arg_data(ArgDecoderConfig::default()); + let int = u64::from_be_bytes(args.context[..].try_into().unwrap()); + let key = DefaultKey::from(KeyData::from_ffi(int)); + let func = TRANSFORMS.with(|transforms| transforms.borrow_mut().remove(key)); + let Some(func) = func else { + crate::trap(&format!("Missing transform function for request {int}")); + }; + let transformed = func(args.response); + reply((transformed,)) + } + + /// Make an HTTP request to a given URL and return the HTTP response, after a transformation. + /// + /// Do not set the `transform` field of `arg`. To use a Candid function, call [`http_request`] instead. + /// + /// See [IC method `http_request`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-http_request). + /// + /// This call requires cycles payment. The required cycles is a function of the request size and max_response_bytes. + /// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost) for more details. + #[cfg_attr(docsrs, doc(cfg(feature = "transform-closure")))] + pub async fn http_request_with_closure( + arg: HttpRequestArgs, + cycles: u128, + transform_func: impl FnOnce(HttpRequestResult) -> HttpRequestResult + 'static, + ) -> CallResult { + assert!( + arg.transform.is_none(), + "The `transform` field in `HttpRequestArgs` must be `None` when using a closure" + ); + let transform_func = Box::new(transform_func) as _; + let key = TRANSFORMS.with(|transforms| transforms.borrow_mut().insert(transform_func)); + struct DropGuard(DefaultKey); + impl Drop for DropGuard { + fn drop(&mut self) { + TRANSFORMS.with(|transforms| transforms.borrow_mut().remove(self.0)); + } + } + let key = DropGuard(key); + let context = key.0.data().as_ffi().to_be_bytes().to_vec(); + let arg = HttpRequestArgs { + transform: Some(TransformContext::from_name( + " http_transform".to_string(), + context, + )), + ..arg + }; + http_request(arg, cycles).await + } +} + +#[cfg(feature = "transform-closure")] +pub use transform_closure::http_request_with_closure; + +// http_request END ----------------------------------------------------------- + +// # Threshold ECDSA signature ================================================ + +/// ECDSA KeyId. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct EcdsaKeyId { + /// See [`EcdsaCurve`]. + pub curve: EcdsaCurve, + /// Name. + pub name: String, +} + +/// ECDSA Curve. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, +)] +pub enum EcdsaCurve { + /// secp256k1 + #[serde(rename = "secp256k1")] + Secp256k1, +} + +impl Default for EcdsaCurve { + fn default() -> Self { + Self::Secp256k1 + } +} + +// ecdsa_public_key ----------------------------------------------------------- + +/// Return a SEC1 encoded ECDSA public key for the given canister using the given derivation path. +/// +/// See [IC method `ecdsa_public_key`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-ecdsa_public_key). +pub async fn ecdsa_public_key(arg: EcdsaPublicKeyArgs) -> CallResult { + Call::new(Principal::management_canister(), "ecdsa_public_key") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`ecdsa_public_key`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct EcdsaPublicKeyArgs { + /// Canister id, default to the canister id of the caller if None. + pub canister_id: Option, + /// A vector of variable length byte strings. + pub derivation_path: Vec>, + /// See [EcdsaKeyId]. + pub key_id: EcdsaKeyId, +} + +/// Response Type of [`ecdsa_public_key`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct EcdsaPublicKeyResult { + /// An ECDSA public key encoded in SEC1 compressed form. + #[serde(with = "serde_bytes")] + pub public_key: Vec, + /// Can be used to deterministically derive child keys of the public_key. + #[serde(with = "serde_bytes")] + pub chain_code: Vec, +} + +// ecda_public_key END -------------------------------------------------------- + +// sign_with_ecdsa ------------------------------------------------------------ + +/// Return a new ECDSA signature of the given message_hash that can be separately verified against a derived ECDSA public key. +/// +/// See [IC method `sign_with_ecdsa`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-sign_with_ecdsa). +/// +/// This call requires cycles payment. +/// This method handles the cycles cost under the hood. +/// Check [Threshold signatures](https://internetcomputer.org/docs/current/references/t-sigs-how-it-works) for more details. +pub async fn sign_with_ecdsa(arg: SignWithEcdsaArgs) -> CallResult { + Call::new(Principal::management_canister(), "sign_with_ecdsa") + .with_arg(arg) + .with_guaranteed_response() + .with_cycles(SIGN_WITH_ECDSA_FEE) + .call() + .await +} + +/// https://internetcomputer.org/docs/current/references/t-sigs-how-it-works#fees-for-the-t-ecdsa-production-key +const SIGN_WITH_ECDSA_FEE: u128 = 26_153_846_153; + +/// Argument type of [`sign_with_ecdsa`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct SignWithEcdsaArgs { + /// Hash of the message with length of 32 bytes. + #[serde(with = "serde_bytes")] + pub message_hash: Vec, + /// A vector of variable length byte strings. + pub derivation_path: Vec>, + /// See [EcdsaKeyId]. + pub key_id: EcdsaKeyId, +} + +/// Response type of [`sign_with_ecdsa`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct SignWithEcdsaResult { + /// Encoded as the concatenation of the SEC1 encodings of the two values r and s. + #[serde(with = "serde_bytes")] + pub signature: Vec, +} + +// sign_with_ecdsa END -------------------------------------------------------- + +// # Threshold ECDSA signature END ============================================ + +// # Threshold Schnorr signature ============================================== + +/// Schnorr KeyId. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct SchnorrKeyId { + /// See [`SchnorrAlgorithm`]. + pub algorithm: SchnorrAlgorithm, + /// Name. + pub name: String, +} + +/// Schnorr Algorithm. +#[derive( + CandidType, + Serialize, + Deserialize, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Clone, + Copy, + Default, +)] +pub enum SchnorrAlgorithm { + /// BIP-340 secp256k1. + #[serde(rename = "bip340secp256k1")] + #[default] + Bip340secp256k1, + /// ed25519. + #[serde(rename = "ed25519")] + Ed25519, +} + +// schnorr_public_key ---------------------------------------------------------- + +/// Return a SEC1 encoded Schnorr public key for the given canister using the given derivation path. +/// +/// See [IC method `schnorr_public_key`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-schnorr_public_key). +pub async fn schnorr_public_key(arg: SchnorrPublicKeyArgs) -> CallResult { + Call::new(Principal::management_canister(), "schnorr_public_key") + .with_arg(arg) + .call() + .await +} + +/// Argument Type of [`schnorr_public_key`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct SchnorrPublicKeyArgs { + /// Canister id, default to the canister id of the caller if None. + pub canister_id: Option, + /// A vector of variable length byte strings. + pub derivation_path: Vec>, + /// See [SchnorrKeyId]. + pub key_id: SchnorrKeyId, +} + +/// Response Type of [`schnorr_public_key`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct SchnorrPublicKeyResult { + /// An Schnorr public key encoded in SEC1 compressed form. + #[serde(with = "serde_bytes")] + pub public_key: Vec, + /// Can be used to deterministically derive child keys of the public_key. + #[serde(with = "serde_bytes")] + pub chain_code: Vec, +} + +// schnorr_public_key END ----------------------------------------------------- + +// sign_with_schnorr ---------------------------------------------------------- + +/// Return a new Schnorr signature of the given message that can be separately verified against a derived Schnorr public key. +/// +/// See [IC method `sign_with_schnorr`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-sign_with_schnorr). +/// +/// This call requires cycles payment. +/// This method handles the cycles cost under the hood. +/// Check [Threshold signatures](https://internetcomputer.org/docs/current/references/t-sigs-how-it-works) for more details. +pub async fn sign_with_schnorr(arg: SignWithSchnorrArgs) -> CallResult { + Call::new(Principal::management_canister(), "sign_with_schnorr") + .with_arg(arg) + .with_guaranteed_response() + .with_cycles(SIGN_WITH_SCHNORR_FEE) + .call() + .await +} + +/// https://internetcomputer.org/docs/current/references/t-sigs-how-it-works/#fees-for-the-t-schnorr-production-key +const SIGN_WITH_SCHNORR_FEE: u128 = 26_153_846_153; + +/// Argument Type of [`sign_with_schnorr`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct SignWithSchnorrArgs { + /// Message to be signed. + #[serde(with = "serde_bytes")] + pub message: Vec, + /// A vector of variable length byte strings. + pub derivation_path: Vec>, + /// See [SchnorrKeyId]. + pub key_id: SchnorrKeyId, +} + +/// Response Type of [`sign_with_schnorr`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct SignWithSchnorrResult { + /// The encoding of the signature depends on the key ID's algorithm. + #[serde(with = "serde_bytes")] + pub signature: Vec, +} + +// sign_with_schnorr END ------------------------------------------------------ + +// # Threshold Schnorr signature END ========================================== + +// node_metrics_history ------------------------------------------------------- + +/// Get a time series of node metrics of a subnet. +/// +/// See [IC method `node_metrics_history`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-node-metrics-history). +pub async fn node_metrics_history( + arg: NodeMetricsHistoryArgs, +) -> CallResult { + Call::new(Principal::management_canister(), "node_metrics_history") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`node_metrics_history`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct NodeMetricsHistoryArgs { + /// Subnet ID. + pub subnet_id: Principal, + /// The returned time series will start at this timestamp. + pub start_at_timestamp_nanos: u64, +} + +/// Return type of [`node_metrics_history`]. +pub type NodeMetricsHistoryResult = Vec; + +/// A record in [`NodeMetricsHistoryResult`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct NodeMetricsHistoryRecord { + /// The timestamp of the record. + pub timestamp_nanos: u64, + /// The metrics of the nodes. + pub node_metrics: Vec, +} + +/// Node metrics. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct NodeMetrics { + /// The principal characterizing a node. + pub node_id: Principal, + /// The number of blocks proposed by this node. + pub num_blocks_proposed_total: u64, + /// The number of failed block proposals by this node. + pub num_blocks_failures_total: u64, +} + +// node_metrics_history END --------------------------------------------------- + +// subnet_info ---------------------------------------------------------------- + +/// Get subnet metadata. +/// +/// See [IC method `subnet_info`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-subnet-info). +pub async fn subnet_info(arg: SubnetInfoArgs) -> CallResult { + Call::new(Principal::management_canister(), "subnet_info") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`subnet_info`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct SubnetInfoArgs { + /// Subnet ID. + pub subnet_id: Principal, +} + +/// Result type of [`subnet_info`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct SubnetInfoResult { + /// Replica version of the subnet. + pub replica_version: String, +} + +// subnet_info END ------------------------------------------------------------ + +// # provisional interfaces for the pre-ledger world ========================== + +// provisional_create_canister_with_cycles ------------------------------------ + +/// Create a new canister with specified amount of cycles balance. +/// +/// This method is only available in local development instances. +/// +/// See [IC method `provisional_create_canister_with_cycles`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-provisional_create_canister_with_cycles). +pub async fn provisional_create_canister_with_cycles( + arg: ProvisionalCreateCanisterWithCyclesArgs, +) -> CallResult { + Call::new( + Principal::management_canister(), + "provisional_create_canister_with_cycles", + ) + .with_arg(arg) + .with_guaranteed_response() + .call() + .await +} + +/// Argument type of [`provisional_create_canister_with_cycles`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct ProvisionalCreateCanisterWithCyclesArgs { + /// The created canister will have this amount of cycles. + pub amount: Option, + /// See [CanisterSettings]. + pub settings: Option, +} + +/// Result type of [`provisional_create_canister_with_cycles`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct ProvisionalCreateCanisterWithCyclesResult { + /// Canister ID of the created canister. + pub canister_id: CanisterId, +} + +// provisional_delete_canister_with_cycles END -------------------------------- + +// provisional_top_up_canister ------------------------------------------------ + +/// Add cycles to a canister. +/// +/// This method is only available in local development instances. +/// +/// See [IC method `provisional_top_up_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-provisional_top_up_canister). +pub async fn provisional_top_up_canister(arg: ProvisionalTopUpCanisterArgs) -> CallResult<()> { + Call::new( + Principal::management_canister(), + "provisional_top_up_canister", + ) + .with_arg(arg) + .with_guaranteed_response() + .call() + .await +} + +/// Argument type of [`provisional_top_up_canister`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct ProvisionalTopUpCanisterArgs { + /// Canister ID. + pub canister_id: CanisterId, + /// Amount of cycles to be added. + pub amount: Nat, +} + +// provisional_top_up_canister END -------------------------------------------- + +// # provisional interfaces for the pre-ledger world END ====================== + +// # Canister snapshots ======================================================= + +/// Snapshot ID. +pub type SnapshotId = Vec; + +/// A snapshot of the state of the canister at a given point in time. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub struct Snapshot { + /// ID of the snapshot. + pub id: SnapshotId, + /// The timestamp at which the snapshot was taken. + pub taken_at_timestamp: u64, + /// The size of the snapshot in bytes. + pub total_size: u64, +} + +// take_canister_snapshot ----------------------------------------------------- + +/// Take a snapshot of the specified canister. +/// +/// A snapshot consists of the wasm memory, stable memory, certified variables, wasm chunk store and wasm binary. +/// +/// See [IC method `take_canister_snapshot`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-take_canister_snapshot). +pub async fn take_canister_snapshot( + arg: TakeCanisterSnapshotArgs, +) -> CallResult { + Call::new(Principal::management_canister(), "take_canister_snapshot") + .with_arg(arg) + .with_guaranteed_response() + .call() + .await +} + +/// Argument type of [`take_canister_snapshot`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct TakeCanisterSnapshotArgs { + /// Canister ID. + pub canister_id: CanisterId, + /// An optional snapshot ID to be replaced by the new snapshot. + /// + /// The snapshot identified by the specified ID will be deleted once a new snapshot has been successfully created. + pub replace_snapshot: Option, +} + +/// Return type of [`take_canister_snapshot`]. +pub type TakeCanisterSnapshotReturn = Snapshot; + +// take_canister_snapshot END ------------------------------------------------- + +// load_canister_snapshot ----------------------------------------------------- + +/// Load a snapshot onto the canister. +/// +/// It fails if no snapshot with the specified `snapshot_id` can be found. +/// +/// See [IC method `load_canister_snapshot`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-load_canister_snapshot). +pub async fn load_canister_snapshot(arg: LoadCanisterSnapshotArgs) -> CallResult<()> { + let complete_arg = LoadCanisterSnapshotArgsComplete { + canister_id: arg.canister_id, + snapshot_id: arg.snapshot_id, + sender_canister_version: Some(canister_version()), + }; + Call::new(Principal::management_canister(), "load_canister_snapshot") + .with_arg(complete_arg) + .with_guaranteed_response() + .call() + .await +} + +/// Argument type of [`load_canister_snapshot`]. +/// +/// Please note that this type is a reduced version of [`LoadCanisterSnapshotArgsComplete`]. +/// The `sender_canister_version` field is removed as it is set automatically in [`load_canister_snapshot`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct LoadCanisterSnapshotArgs { + /// Canister ID. + pub canister_id: CanisterId, + /// ID of the snapshot to be loaded. + pub snapshot_id: SnapshotId, +} + +/// Complete argument type of [`load_canister_snapshot`]. +/// +/// Please note that this type is not used directly as the argument of [`load_canister_snapshot`]. +/// The function [`load_canister_snapshot`] takes [`LoadCanisterSnapshotArgs`] instead. +/// +/// If you want to manually call `load_canister_snapshot` (construct and invoke a [`Call`]), you should use this complete type. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct LoadCanisterSnapshotArgsComplete { + /// Canister ID. + pub canister_id: CanisterId, + /// ID of the snapshot to be loaded. + pub snapshot_id: SnapshotId, + /// sender_canister_version must be set to [`canister_version`]. + pub sender_canister_version: Option, +} + +// load_canister_snapshot END ------------------------------------------------- + +// list_canister_snapshots ---------------------------------------------------- + +/// List the snapshots of the canister. +/// +/// See [IC method `list_canister_snapshots`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-list_canister_snapshots). +pub async fn list_canister_snapshots( + arg: ListCanisterSnapshotsArgs, +) -> CallResult { + Call::new(Principal::management_canister(), "list_canister_snapshots") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`list_canister_snapshots`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct ListCanisterSnapshotsArgs { + /// Canister ID. + pub canister_id: CanisterId, +} + +/// Return type of [`list_canister_snapshots`]. +pub type ListCanisterSnapshotsReturn = Vec; + +// list_canister_snapshots END ------------------------------------------------ + +// delete_canister_snapshot --------------------------------------------------- + +/// Delete a specified snapshot that belongs to an existing canister. +/// +/// An error will be returned if the snapshot is not found. +/// +/// See [IC method `delete_canister_snapshot`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-delete_canister_snapshot). +pub async fn delete_canister_snapshot(arg: DeleteCanisterSnapshotArgs) -> CallResult<()> { + Call::new(Principal::management_canister(), "delete_canister_snapshot") + .with_arg(arg) + .call() + .await +} + +/// Argument type of [`delete_canister_snapshot`]. +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, +)] +pub struct DeleteCanisterSnapshotArgs { + /// Canister ID. + pub canister_id: CanisterId, + /// ID of the snapshot to be deleted. + pub snapshot_id: SnapshotId, +} + +// delete_canister_snapshot END ----------------------------------------------- + +// # Canister snapshots END ===================================================