diff --git a/Cargo.lock b/Cargo.lock index 2d3d49d0..06d9de43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3375,6 +3375,7 @@ version = "0.2.2" dependencies = [ "bigdecimal", "candid", + "ic-management-canister-types 0.7.1", "serde", ] diff --git a/crates/icp-canister-interfaces/Cargo.toml b/crates/icp-canister-interfaces/Cargo.toml index 727fe8ae..fcffc9b5 100644 --- a/crates/icp-canister-interfaces/Cargo.toml +++ b/crates/icp-canister-interfaces/Cargo.toml @@ -8,4 +8,5 @@ publish.workspace = true [dependencies] bigdecimal = { workspace = true } candid = { workspace = true } +ic-management-canister-types = { workspace = true } serde = { workspace = true } diff --git a/crates/icp-canister-interfaces/src/cycles_ledger.rs b/crates/icp-canister-interfaces/src/cycles_ledger.rs index 72604f18..b35fc5a0 100644 --- a/crates/icp-canister-interfaces/src/cycles_ledger.rs +++ b/crates/icp-canister-interfaces/src/cycles_ledger.rs @@ -1,7 +1,7 @@ use candid::{CandidType, Nat, Principal}; use serde::Deserialize; -use crate::management_canister::CanisterSettingsArg; +use ic_management_canister_types::CanisterSettings; /// 100m cycles pub const CYCLES_LEDGER_BLOCK_FEE: u128 = 100_000_000; @@ -20,7 +20,7 @@ pub enum SubnetSelectionArg { #[derive(Clone, Debug, CandidType, Deserialize)] pub struct CreationArgs { pub subnet_selection: Option, - pub settings: Option, + pub settings: Option, } #[derive(Clone, Debug, CandidType, Deserialize)] diff --git a/crates/icp-canister-interfaces/src/lib.rs b/crates/icp-canister-interfaces/src/lib.rs index b2344326..9a95c416 100644 --- a/crates/icp-canister-interfaces/src/lib.rs +++ b/crates/icp-canister-interfaces/src/lib.rs @@ -4,7 +4,6 @@ pub mod cycles_minting_canister; pub mod governance; pub mod icp_ledger; pub mod internet_identity; -pub mod management_canister; pub mod nns_migration; pub mod nns_root; pub mod proxy; diff --git a/crates/icp-canister-interfaces/src/management_canister.rs b/crates/icp-canister-interfaces/src/management_canister.rs deleted file mode 100644 index 25a5bceb..00000000 --- a/crates/icp-canister-interfaces/src/management_canister.rs +++ /dev/null @@ -1,33 +0,0 @@ -use candid::{CandidType, Nat, Principal}; -use serde::Deserialize; - -#[derive(Clone, Debug, CandidType, Deserialize)] -pub struct MgmtCreateCanisterArgs { - pub settings: Option, - pub sender_canister_version: Option, -} - -#[derive(Clone, Debug, CandidType, Deserialize)] -pub struct MgmtCreateCanisterResponse { - pub canister_id: Principal, -} - -#[derive(Clone, Debug, CandidType, Deserialize)] -pub struct CanisterSettingsArg { - pub freezing_threshold: Option, - pub controllers: Option>, - pub reserved_cycles_limit: Option, - pub log_visibility: Option, - pub memory_allocation: Option, - pub compute_allocation: Option, -} - -/// Log visibility setting for a canister. -/// Matches the cycles ledger's LogVisibility variant type. -#[derive(Clone, Debug, CandidType, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum LogVisibility { - Controllers, - Public, - AllowedViewers(Vec), -} diff --git a/crates/icp-cli/src/commands/canister/call.rs b/crates/icp-cli/src/commands/canister/call.rs index 51584362..8ef699c9 100644 --- a/crates/icp-cli/src/commands/canister/call.rs +++ b/crates/icp-cli/src/commands/canister/call.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, anyhow, bail}; use candid::types::{Type, TypeInner}; -use candid::{Encode, IDLArgs, Nat, Principal, TypeEnv, types::Function}; +use candid::{IDLArgs, Principal, TypeEnv, types::Function}; use candid_parser::assist; use candid_parser::parse_idl_args; use candid_parser::utils::CandidSource; @@ -12,12 +12,14 @@ use icp::fs; use icp::manifest::InitArgsFormat; use icp::parsers::CyclesAmount; use icp::prelude::*; -use icp_canister_interfaces::proxy::{ProxyArgs, ProxyResult}; use serde::Serialize; use std::io::{self, Write}; use tracing::{error, warn}; -use crate::{commands::args, operations::misc::fetch_canister_metadata}; +use crate::{ + commands::args, operations::misc::fetch_canister_metadata, + operations::proxy::update_or_proxy_raw, +}; /// How to interpret and display the call response blob. #[derive(Debug, Clone, Copy, Default, ValueEnum)] @@ -206,30 +208,7 @@ pub(crate) async fn exec(ctx: &Context, args: &CallArgs) -> Result<(), anyhow::E .context("failed to serialize candid arguments with specific types")?, }; - let res = if let Some(proxy_cid) = args.proxy { - // Route the call through the proxy canister - let proxy_args = ProxyArgs { - canister_id: cid, - method: method.clone(), - args: arg_bytes, - cycles: Nat::from(args.cycles.get()), - }; - let proxy_arg_bytes = - Encode!(&proxy_args).context("failed to encode proxy call arguments")?; - - let proxy_res = agent - .update(&proxy_cid, "proxy") - .with_arg(proxy_arg_bytes) - .await?; - - let proxy_result: (ProxyResult,) = - candid::decode_args(&proxy_res).context("failed to decode proxy canister response")?; - - match proxy_result.0 { - ProxyResult::Ok(ok) => ok.result, - ProxyResult::Err(err) => bail!(err.format_error()), - } - } else if args.query { + let res = if args.query { // Preemptive check: error if Candid shows this is an update method if let Some((_, func)) = &declared_method && !func.is_query() @@ -245,8 +224,15 @@ pub(crate) async fn exec(ctx: &Context, args: &CallArgs) -> Result<(), anyhow::E .call() .await? } else { - // Direct update call to the target canister - agent.update(&cid, &method).with_arg(arg_bytes).await? + update_or_proxy_raw( + &agent, + cid, + &method, + arg_bytes, + args.proxy, + args.cycles.get(), + ) + .await? }; let mut term = Term::buffered_stdout(); diff --git a/crates/icp-cli/src/commands/canister/create.rs b/crates/icp-cli/src/commands/canister/create.rs index 1a67ed74..f2d6e126 100644 --- a/crates/icp-cli/src/commands/canister/create.rs +++ b/crates/icp-cli/src/commands/canister/create.rs @@ -3,10 +3,10 @@ use std::io::stdout; use anyhow::anyhow; use candid::{Nat, Principal}; use clap::{ArgGroup, Args, Parser}; +use ic_management_canister_types::CanisterSettings as MgmtCanisterSettings; use icp::context::Context; use icp::parsers::{CyclesAmount, DurationAmount, MemoryAmount}; use icp::{Canister, context::CanisterSelection, prelude::*}; -use icp_canister_interfaces::management_canister::CanisterSettingsArg; use serde::Serialize; use tracing::info; @@ -112,8 +112,11 @@ pub(crate) struct CreateArgs { } impl CreateArgs { - pub(crate) fn canister_settings_with_default(&self, default: &Canister) -> CanisterSettingsArg { - CanisterSettingsArg { + pub(crate) fn canister_settings_with_default( + &self, + default: &Canister, + ) -> MgmtCanisterSettings { + MgmtCanisterSettings { freezing_threshold: self .settings .freezing_threshold @@ -144,6 +147,7 @@ impl CreateArgs { .compute_allocation .or(default.settings.compute_allocation) .map(Nat::from), + ..Default::default() } } @@ -155,8 +159,8 @@ impl CreateArgs { } } - pub(crate) fn canister_settings(&self) -> CanisterSettingsArg { - CanisterSettingsArg { + pub(crate) fn canister_settings(&self) -> MgmtCanisterSettings { + MgmtCanisterSettings { freezing_threshold: self .settings .freezing_threshold @@ -180,6 +184,7 @@ impl CreateArgs { .clone() .map(|m| Nat::from(m.get())), compute_allocation: self.settings.compute_allocation.map(Nat::from), + ..Default::default() } } } diff --git a/crates/icp-cli/src/operations/create.rs b/crates/icp-cli/src/operations/create.rs index a3aaf6f5..6af1a5e5 100644 --- a/crates/icp-cli/src/operations/create.rs +++ b/crates/icp-cli/src/operations/create.rs @@ -1,24 +1,26 @@ +use std::sync::Arc; + use candid::{Decode, Encode, Nat, Principal}; use ic_agent::{ Agent, AgentError, agent::{Subnet, SubnetType}, }; +use ic_management_canister_types::{ + CanisterIdRecord, CanisterSettings, CreateCanisterArgs as MgmtCreateCanisterArgs, +}; use icp_canister_interfaces::{ cycles_ledger::{ CYCLES_LEDGER_PRINCIPAL, CreateCanisterArgs, CreateCanisterResponse, CreationArgs, SubnetSelectionArg, }, cycles_minting_canister::CYCLES_MINTING_CANISTER_PRINCIPAL, - management_canister::{ - CanisterSettingsArg, MgmtCreateCanisterArgs, MgmtCreateCanisterResponse, - }, - proxy::{ProxyArgs, ProxyResult}, }; use rand::seq::IndexedRandom; use snafu::{OptionExt, ResultExt, Snafu}; -use std::sync::Arc; use tokio::sync::OnceCell; +use super::proxy::{UpdateOrProxyError, update_or_proxy}; + #[derive(Debug, Snafu)] pub enum CreateOperationError { #[snafu(display("failed to encode candid: {source}"))] @@ -51,11 +53,8 @@ pub enum CreateOperationError { #[snafu(display("failed to resolve subnet: {message}"))] SubnetResolution { message: String }, - #[snafu(display("proxy call failed: {message}"))] - ProxyCall { message: String }, - - #[snafu(display("failed to decode proxy canister response: {source}"))] - ProxyDecode { source: candid::Error }, + #[snafu(transparent)] + UpdateOrProxyCall { source: UpdateOrProxyError }, } /// Determines how a new canister is created. @@ -115,7 +114,7 @@ impl CreateOperation { /// - `Err(CreateOperationError)` if an error occurred. pub async fn create( &self, - settings: &CanisterSettingsArg, + settings: &CanisterSettings, ) -> Result { if let CreateTarget::Proxy(proxy) = self.inner.target { return self.create_proxy(settings, proxy).await; @@ -141,7 +140,7 @@ impl CreateOperation { async fn create_ledger( &self, - settings: &CanisterSettingsArg, + settings: &CanisterSettings, selected_subnet: Principal, ) -> Result { let creation_args = CreationArgs { @@ -182,7 +181,7 @@ impl CreateOperation { async fn create_mgmt( &self, - settings: &CanisterSettingsArg, + settings: &CanisterSettings, selected_subnet: &Subnet, ) -> Result { let arg = MgmtCreateCanisterArgs { @@ -207,51 +206,31 @@ impl CreateOperation { ) .await .context(AgentSnafu)?; - let resp = Decode!(&resp, MgmtCreateCanisterResponse).context(CandidDecodeSnafu)?; + let resp: CanisterIdRecord = Decode!(&resp, CanisterIdRecord).context(CandidDecodeSnafu)?; Ok(resp.canister_id) } async fn create_proxy( &self, - settings: &CanisterSettingsArg, + settings: &CanisterSettings, proxy: Principal, ) -> Result { - let mgmt_arg = MgmtCreateCanisterArgs { + let args = MgmtCreateCanisterArgs { settings: Some(settings.clone()), sender_canister_version: None, }; - let mgmt_arg_bytes = Encode!(&mgmt_arg).context(CandidEncodeSnafu)?; - - let proxy_args = ProxyArgs { - canister_id: Principal::management_canister(), - method: "create_canister".to_string(), - args: mgmt_arg_bytes, - cycles: Nat::from(self.inner.cycles), - }; - let proxy_arg_bytes = Encode!(&proxy_args).context(CandidEncodeSnafu)?; - let proxy_res = self - .inner - .agent - .update(&proxy, "proxy") - .with_arg(proxy_arg_bytes) - .await - .context(AgentSnafu)?; - - let proxy_result: (ProxyResult,) = - candid::decode_args(&proxy_res).context(ProxyDecodeSnafu)?; - - match proxy_result.0 { - ProxyResult::Ok(ok) => { - let resp = - Decode!(&ok.result, MgmtCreateCanisterResponse).context(CandidDecodeSnafu)?; - Ok(resp.canister_id) - } - ProxyResult::Err(err) => ProxyCallSnafu { - message: err.format_error(), - } - .fail(), - } + let (result,): (CanisterIdRecord,) = update_or_proxy( + &self.inner.agent, + Principal::management_canister(), + "create_canister", + (args,), + Some(proxy), + self.inner.cycles, + ) + .await?; + + Ok(result.canister_id) } /// 1. If a subnet is explicitly provided, use it diff --git a/crates/icp-cli/src/operations/mod.rs b/crates/icp-cli/src/operations/mod.rs index cc98ce15..643bdeb2 100644 --- a/crates/icp-cli/src/operations/mod.rs +++ b/crates/icp-cli/src/operations/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod candid_compat; pub(crate) mod canister_migration; pub(crate) mod create; pub(crate) mod install; +pub(crate) mod proxy; pub(crate) mod settings; pub(crate) mod snapshot_transfer; pub(crate) mod sync; diff --git a/crates/icp-cli/src/operations/proxy.rs b/crates/icp-cli/src/operations/proxy.rs new file mode 100644 index 00000000..51e0b8b9 --- /dev/null +++ b/crates/icp-cli/src/operations/proxy.rs @@ -0,0 +1,97 @@ +use candid::utils::{ArgumentDecoder, ArgumentEncoder}; +use candid::{Encode, Nat, Principal}; +use ic_agent::Agent; +use icp_canister_interfaces::proxy::{ProxyArgs, ProxyResult}; +use snafu::{ResultExt, Snafu}; + +#[derive(Debug, Snafu)] +pub enum UpdateOrProxyError { + #[snafu(display("failed to encode proxy call arguments: {source}"))] + ProxyEncode { source: candid::Error }, + + #[snafu(display("direct update call failed: {source}"))] + DirectUpdateCall { source: ic_agent::AgentError }, + + #[snafu(display("proxy update call failed: {source}"))] + ProxyUpdateCall { source: ic_agent::AgentError }, + + #[snafu(display("failed to decode proxy canister response: {source}"))] + ProxyDecode { source: candid::Error }, + + #[snafu(display("proxy call failed: {message}"))] + ProxyCall { message: String }, + + #[snafu(display("failed to encode call arguments: {source}"))] + CandidEncode { source: candid::Error }, + + #[snafu(display("failed to decode call response: {source}"))] + CandidDecode { source: candid::Error }, +} + +/// Dispatches a canister update call, optionally routing through a proxy canister. +/// +/// If `proxy` is `None`, makes a direct update call to the target canister. +/// If `proxy` is `Some`, wraps the call in [`ProxyArgs`] and sends it to the +/// proxy canister's `proxy` method, which forwards it to the target. +/// The `cycles` parameter is only used for proxied calls. +pub async fn update_or_proxy_raw( + agent: &Agent, + canister_id: Principal, + method: &str, + arg: Vec, + proxy: Option, + cycles: u128, +) -> Result, UpdateOrProxyError> { + if let Some(proxy_cid) = proxy { + let proxy_args = ProxyArgs { + canister_id, + method: method.to_string(), + args: arg, + cycles: Nat::from(cycles), + }; + let proxy_arg_bytes = Encode!(&proxy_args).context(ProxyEncodeSnafu)?; + + let proxy_res = agent + .update(&proxy_cid, "proxy") + .with_arg(proxy_arg_bytes) + .await + .context(ProxyUpdateCallSnafu)?; + + let proxy_result: (ProxyResult,) = + candid::decode_args(&proxy_res).context(ProxyDecodeSnafu)?; + + match proxy_result.0 { + ProxyResult::Ok(ok) => Ok(ok.result), + ProxyResult::Err(err) => ProxyCallSnafu { + message: err.format_error(), + } + .fail(), + } + } else { + let res = agent + .update(&canister_id, method) + .with_arg(arg) + .await + .context(DirectUpdateCallSnafu)?; + Ok(res) + } +} + +/// Like [`update_or_proxy_raw`], but accepts typed Candid arguments and decodes the response. +pub async fn update_or_proxy( + agent: &Agent, + canister_id: Principal, + method: &str, + args: A, + proxy: Option, + cycles: u128, +) -> Result +where + A: ArgumentEncoder, + R: for<'a> ArgumentDecoder<'a>, +{ + let arg = candid::encode_args(args).context(CandidEncodeSnafu)?; + let res = update_or_proxy_raw(agent, canister_id, method, arg, proxy, cycles).await?; + let decoded: R = candid::decode_args(&res).context(CandidDecodeSnafu)?; + Ok(decoded) +} diff --git a/crates/icp-cli/src/operations/settings.rs b/crates/icp-cli/src/operations/settings.rs index 88323821..1e7b1bc5 100644 --- a/crates/icp-cli/src/operations/settings.rs +++ b/crates/icp-cli/src/operations/settings.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use candid::Principal; use futures::{StreamExt, stream::FuturesOrdered}; use ic_agent::{Agent, AgentError}; -use ic_management_canister_types::{EnvironmentVariable, LogVisibility as IcLogVisibility}; +use ic_management_canister_types::{EnvironmentVariable, LogVisibility}; use ic_utils::interfaces::ManagementCanister; use icp::{Canister, canister::Settings}; use itertools::Itertools; @@ -45,11 +45,11 @@ struct SettingsFailure { /// Compare two LogVisibility values in an order-insensitive manner. /// For AllowedViewers, the principal lists are compared as sets. -fn log_visibility_eq(a: &IcLogVisibility, b: &IcLogVisibility) -> bool { +fn log_visibility_eq(a: &LogVisibility, b: &LogVisibility) -> bool { match (a, b) { - (IcLogVisibility::Controllers, IcLogVisibility::Controllers) => true, - (IcLogVisibility::Public, IcLogVisibility::Public) => true, - (IcLogVisibility::AllowedViewers(va), IcLogVisibility::AllowedViewers(vb)) => { + (LogVisibility::Controllers, LogVisibility::Controllers) => true, + (LogVisibility::Public, LogVisibility::Public) => true, + (LogVisibility::AllowedViewers(va), LogVisibility::AllowedViewers(vb)) => { let set_a: HashSet<_> = va.iter().collect(); let set_b: HashSet<_> = vb.iter().collect(); set_a == set_b @@ -89,8 +89,8 @@ pub(crate) async fn sync_settings( let current_settings = status.settings; // Convert our log_visibility to IC type for comparison and update - let log_visibility_setting: Option = - log_visibility.clone().map(IcLogVisibility::from); + let log_visibility_setting: Option = + log_visibility.clone().map(LogVisibility::from); let environment_variable_setting = if let Some(configured_environment_variables) = &environment_variables { @@ -262,28 +262,28 @@ mod tests { #[test] fn log_visibility_eq_controllers() { assert!(log_visibility_eq( - &IcLogVisibility::Controllers, - &IcLogVisibility::Controllers + &LogVisibility::Controllers, + &LogVisibility::Controllers )); } #[test] fn log_visibility_eq_public() { assert!(log_visibility_eq( - &IcLogVisibility::Public, - &IcLogVisibility::Public + &LogVisibility::Public, + &LogVisibility::Public )); } #[test] fn log_visibility_eq_different_variants() { assert!(!log_visibility_eq( - &IcLogVisibility::Controllers, - &IcLogVisibility::Public + &LogVisibility::Controllers, + &LogVisibility::Public )); assert!(!log_visibility_eq( - &IcLogVisibility::Public, - &IcLogVisibility::Controllers + &LogVisibility::Public, + &LogVisibility::Controllers )); } @@ -293,8 +293,8 @@ mod tests { let p2 = Principal::from_text("2vxsx-fae").unwrap(); assert!(log_visibility_eq( - &IcLogVisibility::AllowedViewers(vec![p1, p2]), - &IcLogVisibility::AllowedViewers(vec![p1, p2]) + &LogVisibility::AllowedViewers(vec![p1, p2]), + &LogVisibility::AllowedViewers(vec![p1, p2]) )); } @@ -305,8 +305,8 @@ mod tests { // Order should not matter assert!(log_visibility_eq( - &IcLogVisibility::AllowedViewers(vec![p1, p2]), - &IcLogVisibility::AllowedViewers(vec![p2, p1]) + &LogVisibility::AllowedViewers(vec![p1, p2]), + &LogVisibility::AllowedViewers(vec![p2, p1]) )); } @@ -317,8 +317,8 @@ mod tests { let p3 = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(); assert!(!log_visibility_eq( - &IcLogVisibility::AllowedViewers(vec![p1, p2]), - &IcLogVisibility::AllowedViewers(vec![p1, p3]) + &LogVisibility::AllowedViewers(vec![p1, p2]), + &LogVisibility::AllowedViewers(vec![p1, p3]) )); } @@ -328,8 +328,8 @@ mod tests { let p2 = Principal::from_text("2vxsx-fae").unwrap(); assert!(!log_visibility_eq( - &IcLogVisibility::AllowedViewers(vec![p1]), - &IcLogVisibility::AllowedViewers(vec![p1, p2]) + &LogVisibility::AllowedViewers(vec![p1]), + &LogVisibility::AllowedViewers(vec![p1, p2]) )); } @@ -338,12 +338,12 @@ mod tests { let p1 = Principal::from_text("aaaaa-aa").unwrap(); assert!(!log_visibility_eq( - &IcLogVisibility::AllowedViewers(vec![p1]), - &IcLogVisibility::Controllers + &LogVisibility::AllowedViewers(vec![p1]), + &LogVisibility::Controllers )); assert!(!log_visibility_eq( - &IcLogVisibility::AllowedViewers(vec![p1]), - &IcLogVisibility::Public + &LogVisibility::AllowedViewers(vec![p1]), + &LogVisibility::Public )); } diff --git a/crates/icp/src/canister/mod.rs b/crates/icp/src/canister/mod.rs index bf90e53e..791ca788 100644 --- a/crates/icp/src/canister/mod.rs +++ b/crates/icp/src/canister/mod.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use candid::{Nat, Principal}; -use icp_canister_interfaces::management_canister::CanisterSettingsArg; +use ic_management_canister_types::{CanisterSettings, LogVisibility}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -14,73 +14,24 @@ pub mod sync; mod script; /// Controls who can read canister logs. -#[derive(Clone, Debug, Default, PartialEq)] -pub enum LogVisibility { - /// Only controllers can view logs. - #[default] - Controllers, - /// Anyone can view logs. - Public, - /// Specific principals can view logs. - AllowedViewers(Vec), -} - -/// Serialization/deserialization representation for LogVisibility. /// Supports both string format ("controllers", "public") and object format ({ allowed_viewers: [...] }). -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(untagged, rename_all = "snake_case")] -enum LogVisibilityDef { +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(untagged)] +pub enum LogVisibilityDef { /// Simple string variants for controllers or public Simple(LogVisibilitySimple), /// Object format with allowed_viewers list AllowedViewers { allowed_viewers: Vec }, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "snake_case")] -enum LogVisibilitySimple { +pub enum LogVisibilitySimple { Controllers, Public, } -impl From for LogVisibilityDef { - fn from(value: LogVisibility) -> Self { - match value { - LogVisibility::Controllers => { - LogVisibilityDef::Simple(LogVisibilitySimple::Controllers) - } - LogVisibility::Public => LogVisibilityDef::Simple(LogVisibilitySimple::Public), - LogVisibility::AllowedViewers(viewers) => LogVisibilityDef::AllowedViewers { - allowed_viewers: viewers, - }, - } - } -} - -impl From for LogVisibility { - fn from(value: LogVisibilityDef) -> Self { - match value { - LogVisibilityDef::Simple(LogVisibilitySimple::Controllers) => { - LogVisibility::Controllers - } - LogVisibilityDef::Simple(LogVisibilitySimple::Public) => LogVisibility::Public, - LogVisibilityDef::AllowedViewers { allowed_viewers } => { - LogVisibility::AllowedViewers(allowed_viewers) - } - } - } -} - -impl Serialize for LogVisibility { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - LogVisibilityDef::from(self.clone()).serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for LogVisibility { +impl<'de> Deserialize<'de> for LogVisibilityDef { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -91,17 +42,17 @@ impl<'de> Deserialize<'de> for LogVisibility { struct LogVisibilityVisitor; impl<'de> Visitor<'de> for LogVisibilityVisitor { - type Value = LogVisibility; + type Value = LogVisibilityDef; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("'controllers', 'public', or object with 'allowed_viewers'") } fn visit_str(self, value: &str) -> Result { - LogVisibilityDef::deserialize( + LogVisibilitySimple::deserialize( serde::de::value::StrDeserializer::::new(value), ) - .map(Into::into) + .map(LogVisibilityDef::Simple) .map_err(|_| { E::custom(format!( "unknown log_visibility value: '{}', expected 'controllers' or 'public'", @@ -131,7 +82,7 @@ impl<'de> Deserialize<'de> for LogVisibility { } allowed_viewers - .map(LogVisibility::AllowedViewers) + .map(|v| LogVisibilityDef::AllowedViewers { allowed_viewers: v }) .ok_or_else(|| Error::missing_field("allowed_viewers")) } } @@ -140,7 +91,7 @@ impl<'de> Deserialize<'de> for LogVisibility { } } -impl JsonSchema for LogVisibility { +impl JsonSchema for LogVisibilityDef { fn schema_name() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed("LogVisibility") } @@ -175,26 +126,15 @@ impl JsonSchema for LogVisibility { } } -impl From for ic_management_canister_types::LogVisibility { - fn from(value: LogVisibility) -> Self { +impl From for LogVisibility { + fn from(value: LogVisibilityDef) -> Self { match value { - LogVisibility::Controllers => ic_management_canister_types::LogVisibility::Controllers, - LogVisibility::Public => ic_management_canister_types::LogVisibility::Public, - LogVisibility::AllowedViewers(viewers) => { - ic_management_canister_types::LogVisibility::AllowedViewers(viewers) + LogVisibilityDef::Simple(LogVisibilitySimple::Controllers) => { + LogVisibility::Controllers } - } - } -} - -impl From for icp_canister_interfaces::management_canister::LogVisibility { - fn from(value: LogVisibility) -> Self { - use icp_canister_interfaces::management_canister::LogVisibility as CyclesLedgerLogVisibility; - match value { - LogVisibility::Controllers => CyclesLedgerLogVisibility::Controllers, - LogVisibility::Public => CyclesLedgerLogVisibility::Public, - LogVisibility::AllowedViewers(viewers) => { - CyclesLedgerLogVisibility::AllowedViewers(viewers) + LogVisibilityDef::Simple(LogVisibilitySimple::Public) => LogVisibility::Public, + LogVisibilityDef::AllowedViewers { allowed_viewers } => { + LogVisibility::AllowedViewers(allowed_viewers) } } } @@ -204,7 +144,7 @@ impl From for icp_canister_interfaces::management_canister::LogVi #[derive(Clone, Debug, Default, Deserialize, PartialEq, JsonSchema, Serialize)] pub struct Settings { /// Controls who can read canister logs. - pub log_visibility: Option, + pub log_visibility: Option, /// Compute allocation (0 to 100). Represents guaranteed compute capacity. pub compute_allocation: Option, @@ -241,15 +181,16 @@ pub struct Settings { pub environment_variables: Option>, } -impl From for CanisterSettingsArg { +impl From for CanisterSettings { fn from(settings: Settings) -> Self { - CanisterSettingsArg { + CanisterSettings { freezing_threshold: settings.freezing_threshold.map(|d| Nat::from(d.get())), controllers: None, reserved_cycles_limit: settings.reserved_cycles_limit.map(|c| Nat::from(c.get())), log_visibility: settings.log_visibility.map(Into::into), memory_allocation: settings.memory_allocation.map(|m| Nat::from(m.get())), compute_allocation: settings.compute_allocation.map(Nat::from), + ..Default::default() } } } @@ -261,15 +202,21 @@ mod tests { #[test] fn log_visibility_deserialize_controllers() { let yaml = "controllers"; - let result: LogVisibility = serde_yaml::from_str(yaml).unwrap(); - assert_eq!(result, LogVisibility::Controllers); + let result: LogVisibilityDef = serde_yaml::from_str(yaml).unwrap(); + assert_eq!( + result, + LogVisibilityDef::Simple(LogVisibilitySimple::Controllers) + ); } #[test] fn log_visibility_deserialize_public() { let yaml = "public"; - let result: LogVisibility = serde_yaml::from_str(yaml).unwrap(); - assert_eq!(result, LogVisibility::Public); + let result: LogVisibilityDef = serde_yaml::from_str(yaml).unwrap(); + assert_eq!( + result, + LogVisibilityDef::Simple(LogVisibilitySimple::Public) + ); } #[test] @@ -279,12 +226,18 @@ allowed_viewers: - "aaaaa-aa" - "2vxsx-fae" "#; - let result: LogVisibility = serde_yaml::from_str(yaml).unwrap(); + let result: LogVisibilityDef = serde_yaml::from_str(yaml).unwrap(); match result { - LogVisibility::AllowedViewers(viewers) => { - assert_eq!(viewers.len(), 2); - assert_eq!(viewers[0], Principal::from_text("aaaaa-aa").unwrap()); - assert_eq!(viewers[1], Principal::from_text("2vxsx-fae").unwrap()); + LogVisibilityDef::AllowedViewers { allowed_viewers } => { + assert_eq!(allowed_viewers.len(), 2); + assert_eq!( + allowed_viewers[0], + Principal::from_text("aaaaa-aa").unwrap() + ); + assert_eq!( + allowed_viewers[1], + Principal::from_text("2vxsx-fae").unwrap() + ); } _ => panic!("Expected AllowedViewers variant"), } @@ -293,10 +246,10 @@ allowed_viewers: #[test] fn log_visibility_deserialize_allowed_viewers_empty() { let yaml = "allowed_viewers: []"; - let result: LogVisibility = serde_yaml::from_str(yaml).unwrap(); + let result: LogVisibilityDef = serde_yaml::from_str(yaml).unwrap(); match result { - LogVisibility::AllowedViewers(viewers) => { - assert!(viewers.is_empty()); + LogVisibilityDef::AllowedViewers { allowed_viewers } => { + assert!(allowed_viewers.is_empty()); } _ => panic!("Expected AllowedViewers variant"), } @@ -305,7 +258,7 @@ allowed_viewers: #[test] fn log_visibility_deserialize_invalid_string() { let yaml = "invalid"; - let result: Result = serde_yaml::from_str(yaml); + let result: Result = serde_yaml::from_str(yaml); assert!(result.is_err()); let err = result.unwrap_err().to_string(); assert!(err.contains("unknown log_visibility value")); @@ -314,7 +267,7 @@ allowed_viewers: #[test] fn log_visibility_deserialize_invalid_field() { let yaml = "unknown_field: []"; - let result: Result = serde_yaml::from_str(yaml); + let result: Result = serde_yaml::from_str(yaml); assert!(result.is_err()); let err = result.unwrap_err().to_string(); assert!(err.contains("unknown field")); @@ -322,24 +275,26 @@ allowed_viewers: #[test] fn log_visibility_serialize_controllers() { - let log_vis = LogVisibility::Controllers; + let log_vis = LogVisibilityDef::Simple(LogVisibilitySimple::Controllers); let yaml = serde_yaml::to_string(&log_vis).unwrap(); assert_eq!(yaml.trim(), "controllers"); } #[test] fn log_visibility_serialize_public() { - let log_vis = LogVisibility::Public; + let log_vis = LogVisibilityDef::Simple(LogVisibilitySimple::Public); let yaml = serde_yaml::to_string(&log_vis).unwrap(); assert_eq!(yaml.trim(), "public"); } #[test] fn log_visibility_serialize_allowed_viewers() { - let log_vis = LogVisibility::AllowedViewers(vec![ - Principal::from_text("aaaaa-aa").unwrap(), - Principal::from_text("2vxsx-fae").unwrap(), - ]); + let log_vis = LogVisibilityDef::AllowedViewers { + allowed_viewers: vec![ + Principal::from_text("aaaaa-aa").unwrap(), + Principal::from_text("2vxsx-fae").unwrap(), + ], + }; let yaml = serde_yaml::to_string(&log_vis).unwrap(); assert!(yaml.contains("allowed_viewers")); assert!(yaml.contains("aaaaa-aa")); @@ -418,25 +373,20 @@ allowed_viewers: #[test] fn log_visibility_conversion_to_ic_type() { - let controllers = LogVisibility::Controllers; - let ic_controllers: ic_management_canister_types::LogVisibility = controllers.into(); - assert!(matches!( - ic_controllers, - ic_management_canister_types::LogVisibility::Controllers - )); - - let public = LogVisibility::Public; - let ic_public: ic_management_canister_types::LogVisibility = public.into(); - assert!(matches!( - ic_public, - ic_management_canister_types::LogVisibility::Public - )); - - let viewers = - LogVisibility::AllowedViewers(vec![Principal::from_text("aaaaa-aa").unwrap()]); - let ic_viewers: ic_management_canister_types::LogVisibility = viewers.into(); + let controllers = LogVisibilityDef::Simple(LogVisibilitySimple::Controllers); + let ic_controllers: LogVisibility = controllers.into(); + assert!(matches!(ic_controllers, LogVisibility::Controllers)); + + let public = LogVisibilityDef::Simple(LogVisibilitySimple::Public); + let ic_public: LogVisibility = public.into(); + assert!(matches!(ic_public, LogVisibility::Public)); + + let viewers = LogVisibilityDef::AllowedViewers { + allowed_viewers: vec![Principal::from_text("aaaaa-aa").unwrap()], + }; + let ic_viewers: LogVisibility = viewers.into(); match ic_viewers { - ic_management_canister_types::LogVisibility::AllowedViewers(v) => { + LogVisibility::AllowedViewers(v) => { assert_eq!(v.len(), 1); } _ => panic!("Expected AllowedViewers"), diff --git a/crates/icp/src/network/managed/run.rs b/crates/icp/src/network/managed/run.rs index dcc47eca..ece48d1b 100644 --- a/crates/icp/src/network/managed/run.rs +++ b/crates/icp/src/network/managed/run.rs @@ -8,6 +8,7 @@ use ic_agent::{ identity::{AnonymousIdentity, Secp256k1Identity}, }; use ic_ledger_types::{AccountIdentifier, Memo, Subaccount, Tokens, TransferArgs, TransferResult}; +use ic_management_canister_types::CanisterSettings; use ic_utils::interfaces::management_canister::builders::CanisterInstallMode; use icp_canister_interfaces::{ cycles_ledger::{ @@ -19,7 +20,6 @@ use icp_canister_interfaces::{ NotifyMintArgs, NotifyMintResponse, }, icp_ledger::{ICP_LEDGER_BLOCK_FEE_E8S, ICP_LEDGER_PRINCIPAL}, - management_canister::CanisterSettingsArg, }; use icrc_ledger_types::icrc1::{ account::Account, @@ -842,13 +842,9 @@ async fn install_proxy( let creation_args = if !controllers.is_empty() { Some(CreationArgs { subnet_selection: None, - settings: Some(CanisterSettingsArg { + settings: Some(CanisterSettings { controllers: Some(controllers.clone()), - freezing_threshold: None, - reserved_cycles_limit: None, - log_visibility: None, - memory_allocation: None, - compute_allocation: None, + ..Default::default() }), }) } else {