diff --git a/Cargo.lock b/Cargo.lock index e5c64b40..a47d2492 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1227,7 +1227,9 @@ dependencies = [ "futures-util", "ic-agent", "once_cell", + "pocket-ic", "rand", + "reqwest", "semver", "serde", "serde_bytes", @@ -1300,7 +1302,9 @@ dependencies = [ "humantime", "ic-agent", "ic-utils", + "pocket-ic", "rand", + "reqwest", "serde", "serde_json", "tokio", diff --git a/ic-utils/Cargo.toml b/ic-utils/Cargo.toml index 4ae5f8f5..35587555 100644 --- a/ic-utils/Cargo.toml +++ b/ic-utils/Cargo.toml @@ -33,7 +33,9 @@ once_cell = "1.10.0" [dev-dependencies] ed25519-consensus = { workspace = true } ic-agent = { workspace = true, default-features = true } +pocket-ic = { workspace = true } rand = { workspace = true } +reqwest = { workspace = true } tokio = { workspace = true, features = ["full"] } [features] diff --git a/ic-utils/src/canister.rs b/ic-utils/src/canister.rs index 50f88100..af84e2e1 100644 --- a/ic-utils/src/canister.rs +++ b/ic-utils/src/canister.rs @@ -393,8 +393,28 @@ mod tests { use ic_agent::identity::BasicIdentity; use rand::thread_rng; - fn get_effective_canister_id() -> Principal { - Principal::from_text("rwlgt-iiaaa-aaaaa-aaaaa-cai").unwrap() + const POCKET_IC: &str = "POCKET_IC"; + + async fn get_effective_canister_id() -> Principal { + if let Ok(pocket_ic_url) = std::env::var(POCKET_IC) { + let client = reqwest::Client::new(); + let topology: pocket_ic::common::rest::Topology = client + .get(format!("{}{}", pocket_ic_url, "/_/topology")) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); + let app_subnet = topology.get_app_subnets()[0]; + Principal::from_slice( + &topology.0.get(&app_subnet).unwrap().canister_ranges[0] + .start + .canister_id, + ) + } else { + Principal::from_text("rwlgt-iiaaa-aaaaa-aaaaa-cai").unwrap() + } } #[ignore] @@ -424,7 +444,7 @@ mod tests { let (new_canister_id,) = management_canister .create_canister() .as_provisional_create_with_amount(None) - .with_effective_canister_id(get_effective_canister_id()) + .with_effective_canister_id(get_effective_canister_id().await) .call_and_wait() .await .unwrap(); diff --git a/icx/Cargo.toml b/icx/Cargo.toml index db8f197f..c1ac315c 100644 --- a/icx/Cargo.toml +++ b/icx/Cargo.toml @@ -28,7 +28,9 @@ hex = { workspace = true } humantime = "2.0.1" ic-agent = { workspace = true, default-features = true } ic-utils = { workspace = true } +pocket-ic = { workspace = true } rand = { workspace = true } +reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = ["full"] } diff --git a/icx/src/main.rs b/icx/src/main.rs index 450ba721..8d93ad30 100644 --- a/icx/src/main.rs +++ b/icx/src/main.rs @@ -268,7 +268,7 @@ pub fn get_effective_canister_id( method_name: &str, arg_value: &[u8], canister_id: Principal, -) -> Result { +) -> Result> { if is_management_canister { let method_name = MgmtMethod::from_str(method_name).with_context(|| { format!( @@ -284,7 +284,7 @@ pub fn get_effective_canister_id( MgmtMethod::InstallCode => { let install_args = Decode!(arg_value, CanisterInstall) .context("Argument is not valid for CanisterInstall")?; - Ok(install_args.canister_id) + Ok(Some(install_args.canister_id)) } MgmtMethod::StartCanister | MgmtMethod::StopCanister @@ -307,9 +307,9 @@ pub fn get_effective_canister_id( } let in_args = Decode!(arg_value, In).context("Argument is not a valid Principal")?; - Ok(in_args.canister_id) + Ok(Some(in_args.canister_id)) } - MgmtMethod::ProvisionalCreateCanisterWithCycles => Ok(Principal::management_canister()), + MgmtMethod::ProvisionalCreateCanisterWithCycles => Ok(None), MgmtMethod::UpdateSettings => { #[derive(CandidType, Deserialize)] struct In { @@ -318,7 +318,7 @@ pub fn get_effective_canister_id( } let in_args = Decode!(arg_value, In).context("Argument is not valid for UpdateSettings")?; - Ok(in_args.canister_id) + Ok(Some(in_args.canister_id)) } MgmtMethod::InstallChunkedCode => { #[derive(CandidType, Deserialize)] @@ -327,7 +327,7 @@ pub fn get_effective_canister_id( } let in_args = Decode!(arg_value, In) .context("Argument is not valid for InstallChunkedCode")?; - Ok(in_args.target_canister) + Ok(Some(in_args.target_canister)) } MgmtMethod::BitcoinGetBalance | MgmtMethod::BitcoinGetUtxos @@ -340,7 +340,7 @@ pub fn get_effective_canister_id( } } } else { - Ok(canister_id) + Ok(Some(canister_id)) } } @@ -362,6 +362,35 @@ async fn main() -> Result<()> { .build() .context("Failed to build the Agent")?; + let get_default_effective_canister_id = || async { + let client = reqwest::Client::new(); + let topology: pocket_ic::common::rest::Topology = client + .get(format!( + "{}{}", + opts.replica.trim_end_matches('/'), + "/_/topology" + )) + .send() + .await? + .json() + .await?; + let subnet = topology.get_app_subnets().into_iter().next().unwrap_or_else(|| + topology + .get_verified_app_subnets() + .into_iter() + .next() + .unwrap_or_else(|| topology.get_system_subnets().into_iter().next().unwrap_or_else(|| panic!("PocketIC topology contains no application, verified application, and system subnet."))), + ); + Ok::<_, reqwest::Error>(Principal::from_slice( + &topology.0.get(&subnet).unwrap().canister_ranges[0] + .start + .canister_id, + )) + }; + let default_effective_canister_id = get_default_effective_canister_id() + .await + .unwrap_or(Principal::management_canister()); + // You can handle information about subcommands by requesting their matches by name // (as below), requesting just the name used, or both at the same time match &opts.subcommand { @@ -384,7 +413,8 @@ async fn main() -> Result<()> { &arg, t.canister_id, ) - .context("Failed to get effective_canister_id for this call")?; + .context("Failed to get effective_canister_id for this call")? + .unwrap_or(default_effective_canister_id); if !t.serialize { let result = match &opts.subcommand { diff --git a/ref-tests/tests/ic-ref.rs b/ref-tests/tests/ic-ref.rs index 1cc6174f..15720e9e 100644 --- a/ref-tests/tests/ic-ref.rs +++ b/ref-tests/tests/ic-ref.rs @@ -764,7 +764,7 @@ mod management_canister { .await?; assert!( - result.cycles > 0_u64 && result.cycles < creation_fee, + result.cycles > 0_u64 && result.cycles < creation_fee + canister_initial_balance, "expected 0..{creation_fee}, got {}", result.cycles ); @@ -894,13 +894,25 @@ mod management_canister { #[ignore] #[test] fn subnet_metrics() { - with_universal_canister(|agent, _| async move { + with_agent(|agent| async move { + let ic00 = ManagementCanister::create(&agent); + + // create a canister on the root subnet + let registry_canister_id = Principal::from_text("rwlgt-iiaaa-aaaaa-aaaaa-cai").unwrap(); + let _ = ic00 + .create_canister() + .as_provisional_create_with_amount(None) + .with_effective_canister_id(registry_canister_id) + .call_and_wait() + .await?; + + // fetch root subnet metrics let metrics = agent .read_state_subnet_metrics(Principal::self_authenticating(&agent.read_root_key())) .await?; assert!( metrics.num_canisters >= 1, - "expected universal canister in num_canisters" + "expected a canister on the root subnet" ); Ok(()) })