diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index d233f6bcae..5de5352f2e 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -2,6 +2,12 @@ == DFX +=== feat: add --no-wallet flag and --wallet option to allow Users to bypass Wallet or specify a Wallet to use for calls (#1476) + +Added `--no-wallet` flag to `dfx canister` and `dfx deploy`. This allows users to call canister management functionality with their Identity as the Sender (bypassing their Wallet canister.) +Added `--wallet` option to `dfx canister` and `dfx deploy`. This allows users to specify a wallet canister id to use as the Sender for calls. +`--wallet` and `--no-wallet` conflict with each other. Omitting both will invoke the selected Identity's wallet canister to perform calls. + === feat: implement the HTTP Request proposal in dfx' bootstrap webserver. + And add support for http requests in the base storage canister (with a default to `/index.html`). diff --git a/e2e/assets/initializer/initializer.mo b/e2e/assets/initializer/initializer.mo new file mode 100644 index 0000000000..ec794273b4 --- /dev/null +++ b/e2e/assets/initializer/initializer.mo @@ -0,0 +1,7 @@ +import Error "mo:base/Error"; + +shared ({caller = initializer}) actor class() { + public shared (message) func test(): async Bool { + message.caller == initializer + } +} diff --git a/e2e/assets/initializer/patch.bash b/e2e/assets/initializer/patch.bash new file mode 100644 index 0000000000..d0f7ed39e0 --- /dev/null +++ b/e2e/assets/initializer/patch.bash @@ -0,0 +1 @@ +dfx config canisters/e2e_project/main initializer.mo diff --git a/e2e/tests-dfx/basic-project.bash b/e2e/tests-dfx/basic-project.bash index f23b670ed6..aede89d8b0 100644 --- a/e2e/tests-dfx/basic-project.bash +++ b/e2e/tests-dfx/basic-project.bash @@ -33,11 +33,19 @@ teardown() { assert_eq '("Hello, Bongalo!")' # Using call --async and request-status. - assert_command dfx canister call --async hello greet Blueberry + # Call with user Identity as Sender + assert_command dfx canister --no-wallet call --async hello greet Blueberry # At this point $output is the request ID. # shellcheck disable=SC2154 assert_command dfx canister request-status "$stdout" assert_eq '("Hello, Blueberry!")' + + # Call using the wallet's call forwarding + assert_command dfx canister call --async hello greet Blueberry + # At this point $output is the request ID. + # shellcheck disable=SC2154 + assert_command dfx canister request-status "$stdout" + assert_eq '(record { 153_986_224 = blob "DIDL\00\01q\11Hello, Blueberry!" })' } @test "build + install + call + request-status -- counter_mo" { @@ -79,9 +87,16 @@ teardown() { assert_eq "()" # Write has no return value. But we can _call_ read too. - assert_command dfx canister call hello read --async + # Call with user Identity as Sender + assert_command dfx canister --no-wallet call hello read --async assert_command dfx canister request-status "$stdout" assert_eq "(1_337)" + + # Call using the wallet's call forwarding + assert_command dfx canister call hello read --async + assert_command dfx canister request-status "$stdout" + assert_eq '(record { 153_986_224 = blob "DIDL\00\01}\b9\0a" })' + } @test "build + install + call -- counter_idl_mo" { diff --git a/e2e/tests-dfx/certificate.bash b/e2e/tests-dfx/certificate.bash index 5ffff42812..a89c3cded9 100644 --- a/e2e/tests-dfx/certificate.bash +++ b/e2e/tests-dfx/certificate.bash @@ -60,6 +60,9 @@ teardown() { } @test "mitm attack - query: attack succeeds because there is no certificate to verify" { - assert_command dfx canister call certificate hello_query '("Buckaroo")' + # The wallet does not have a query call forward method (currently calls forward from wallet's update method) + # So call with users Identity as sender here + # There may need to be a query version of wallet_call + assert_command dfx canister --no-wallet call certificate hello_query '("Buckaroo")' assert_eq '("Hullo, Buckaroo!")' } diff --git a/e2e/tests-dfx/identity.bash b/e2e/tests-dfx/identity.bash index 9e37910f9c..3e9381a60f 100644 --- a/e2e/tests-dfx/identity.bash +++ b/e2e/tests-dfx/identity.bash @@ -88,27 +88,32 @@ teardown() { assert_command dfx --identity alice build assert_command dfx --identity alice canister install --all + # The wallet is the initializer assert_command dfx --identity alice canister call e2e_project amInitializer + assert_eq '(true)' + + # The user Identity's principal is not the initializer + assert_command dfx --identity alice canister --no-wallet call e2e_project amInitializer assert_eq '(false)' - assert_command dfx --identity alice canister call \ + assert_command dfx --identity alice canister --no-wallet call \ "$(dfx --identity alice identity get-wallet)" wallet_call \ "(record { canister = principal \"$(dfx canister id e2e_project)\"; method_name = \"amInitializer\"; args = blob \"DIDL\00\00\"; cycles = (0:nat64)})" assert_eq '(record { 153_986_224 = blob "DIDL\00\01~\01" })' # True in DIDL. - assert_command dfx --identity bob canister call e2e_project amInitializer + assert_command dfx --identity bob canister --no-wallet call e2e_project amInitializer assert_eq '(false)' # these all fail (other identities are not initializer; cannot store assets): - assert_command_fail dfx --identity bob canister call e2e_project_assets store '("B", vec { 88; 87; 86 })' - assert_command_fail dfx --identity default canister call e2e_project_assets store '("B", vec { 88; 87; 86 })' - assert_command_fail dfx canister call e2e_project_assets store '("B", vec { 88; 87; 86 })' - assert_command_fail dfx canister call e2e_project_assets retrieve '("B")' + assert_command_fail dfx --identity bob canister --no-wallet call e2e_project_assets store '("B", vec { 88; 87; 86 })' + assert_command_fail dfx --identity default canister --no-wallet call e2e_project_assets store '("B", vec { 88; 87; 86 })' + assert_command_fail dfx canister --no-wallet call e2e_project_assets store '("B", vec { 88; 87; 86 })' + assert_command_fail dfx canister --no-wallet call e2e_project_assets retrieve '("B")' # but alice, the initializer, can store assets: assert_command dfx --identity alice canister call e2e_project_assets store '("B", vec { 88; 87; 86 })' assert_eq '()' - assert_command dfx canister call --output idl e2e_project_assets retrieve '("B")' + assert_command dfx canister --no-wallet call --output idl e2e_project_assets retrieve '("B")' assert_eq '(blob "XWV")' } @@ -120,24 +125,24 @@ teardown() { dfx --identity alice canister create --all assert_command dfx --identity alice build assert_command dfx --identity alice canister install --all - assert_command dfx --identity alice canister call \ + assert_command dfx --identity alice canister --no-wallet call \ "$(dfx --identity alice identity get-wallet)" wallet_call \ "(record { canister = principal \"$(dfx canister id e2e_project)\"; method_name = \"amInitializer\"; args = blob \"DIDL\00\00\"; cycles = (0:nat64)})" assert_eq '(record { 153_986_224 = blob "DIDL\00\01~\01" })' # True in DIDL. - assert_command dfx canister call e2e_project amInitializer + assert_command dfx canister --no-wallet call e2e_project amInitializer assert_eq '(false)' assert_command dfx identity rename alice bob assert_command dfx identity whoami assert_eq 'default' - assert_command dfx --identity bob canister call \ + assert_command dfx --identity bob canister --no-wallet call \ "$(dfx --identity bob identity get-wallet)" wallet_call \ "(record { canister = principal \"$(dfx canister id e2e_project)\"; method_name = \"amInitializer\"; args = blob \"DIDL\00\00\"; cycles = (0:nat64)})" assert_eq '(record { 153_986_224 = blob "DIDL\00\01~\01" })' # True in DIDL. assert_command dfx --identity bob canister call e2e_project_assets store '("B", blob "hello")' assert_eq '()' - assert_command dfx canister call --output idl e2e_project_assets retrieve '("B")' + assert_command dfx canister --no-wallet call --output idl e2e_project_assets retrieve '("B")' assert_eq '(blob "hello")' } diff --git a/e2e/tests-dfx/initializer.bash b/e2e/tests-dfx/initializer.bash new file mode 100644 index 0000000000..8808084212 --- /dev/null +++ b/e2e/tests-dfx/initializer.bash @@ -0,0 +1,40 @@ +#!/usr/bin/env bats + +load ../utils/_ + +setup() { + # We want to work from a temporary directory, different for every test. + cd "$(mktemp -d -t dfx-e2e-XXXXXXXX)" || exit + + # Each test gets its own home directory in order to have its own identities. + x=$(pwd)/home-for-test + mkdir "$x" + export HOME="$x" + + dfx_new +} + +teardown() { + dfx_stop + x=$(pwd)/home-for-test + rm -rf "$x" +} + +@test "test access control flow via dfx call" { + install_asset initializer + dfx_start + assert_command dfx identity new alice + assert_command dfx identity use alice + + dfx canister create --all + assert_command dfx build + assert_command dfx canister install --all + + # The wallet is the initializer + assert_command dfx canister call e2e_project test + assert_eq '(true)' + + # The user Identity's principal is not the initializer + assert_command dfx canister --no-wallet call e2e_project test + assert_eq '(false)' +} diff --git a/e2e/tests-dfx/wallet.bash b/e2e/tests-dfx/wallet.bash index 4dfc514843..da010e447e 100644 --- a/e2e/tests-dfx/wallet.bash +++ b/e2e/tests-dfx/wallet.bash @@ -10,8 +10,6 @@ setup() { x=$(pwd)/home-for-test mkdir "$x" export HOME="$x" - - dfx_new } teardown() { @@ -21,6 +19,7 @@ teardown() { } @test "deploy wallet" { + dfx_new hello dfx_start setup_actuallylocal_network @@ -45,3 +44,36 @@ teardown() { assert_command dfx identity --network actuallylocal deploy-wallet "${ID_TWO}" assert_match "The wallet canister \"${ID}\"\ already exists for user \"default\" on \"actuallylocal\" network." } + +@test "bypass wallet call as user" { + dfx_new + install_asset identity + dfx_start + assert_command dfx canister --no-wallet create --all + assert_command dfx build + assert_command dfx canister --no-wallet install --all + + CALL_RES=$(dfx canister --no-wallet call e2e_project fromCall) + CALLER=$(echo "${CALL_RES}" | cut -d'"' -f 2) + ID=$(dfx identity get-principal) + assert_eq "$CALLER" "$ID" + + assert_command dfx canister --no-wallet call e2e_project amInitializer + assert_eq '(true)' +} + +@test "bypass wallet call as user: deploy" { + dfx_new + install_asset identity + dfx_start + assert_command dfx deploy --no-wallet + + CALL_RES=$(dfx canister --no-wallet call e2e_project fromCall) + CALLER=$(echo "${CALL_RES}" | cut -d'"' -f 2) + ID=$(dfx identity get-principal) + assert_eq "$CALLER" "$ID" + + assert_command dfx canister --no-wallet call e2e_project amInitializer + assert_eq '(true)' +} + diff --git a/src/dfx/src/commands/canister/call.rs b/src/dfx/src/commands/canister/call.rs index bd9b9aab38..2231e1ade9 100644 --- a/src/dfx/src/commands/canister/call.rs +++ b/src/dfx/src/commands/canister/call.rs @@ -1,14 +1,21 @@ use crate::lib::canister_info::CanisterInfo; use crate::lib::environment::Environment; -use crate::lib::error::DfxResult; +use crate::lib::error::{DfxError, DfxResult}; +use crate::lib::identity::identity_utils::CallSender; +use crate::lib::identity::Identity; use crate::lib::models::canister_id_store::CanisterIdStore; use crate::lib::root_key::fetch_root_key_if_needed; use crate::lib::waiter::waiter_with_exponential_backoff; +use crate::util::clap::validators::cycle_amount_validator; use crate::util::{blob_from_arguments, expiry_duration, get_candid_type, print_idl_blob}; use anyhow::{anyhow, bail, Context}; +use candid::{CandidType, Deserialize}; use clap::Clap; use ic_types::principal::Principal as CanisterId; +use ic_utils::canister::{Argument, Canister}; +use ic_utils::interfaces::wallet::{CallForwarder, CallResult}; +use ic_utils::interfaces::Wallet; use std::option::Option; use std::path::PathBuf; @@ -50,6 +57,20 @@ pub struct CanisterCallOpts { #[clap(long, conflicts_with("async"), possible_values(&["idl", "raw", "pp"]))] output: Option, + + /// Specifies the amount of cycles to send on the call. + /// Deducted from the wallet. + #[clap(long, validator(cycle_amount_validator))] + with_cycles: Option, +} + +#[derive(CandidType, Deserialize)] +struct CallIn { + canister: CanisterId, + method_name: String, + #[serde(with = "serde_bytes")] + args: Vec, + cycles: u64, } fn get_local_cid_and_candid_path( @@ -65,7 +86,36 @@ fn get_local_cid_and_candid_path( )) } -pub async fn exec(env: &dyn Environment, opts: CanisterCallOpts) -> DfxResult { +async fn do_wallet_call(wallet: &Canister<'_, Wallet>, args: &CallIn) -> DfxResult> { + let (result,): (CallResult,) = wallet + .update_("wallet_call") + .with_arg(args) + .build() + .call_and_wait(waiter_with_exponential_backoff()) + .await?; + Ok(result.r#return) +} + +async fn request_id_via_wallet_call( + wallet: &Canister<'_, Wallet>, + canister: &Canister<'_>, + method_name: &str, + args: Argument, + cycles: u64, +) -> DfxResult { + let call_forwarder: CallForwarder<'_, '_, (CallResult,)> = + wallet.call(canister, method_name, args, cycles); + call_forwarder + .call() + .await + .map_err(|err| anyhow!("Agent error {}", err)) +} + +pub async fn exec( + env: &dyn Environment, + opts: CanisterCallOpts, + call_sender: &CallSender, +) -> DfxResult { let callee_canister = opts.canister_name.as_str(); let method_name = opts.method_name.as_str(); let canister_id_store = CanisterIdStore::for_env(env)?; @@ -124,29 +174,87 @@ pub async fn exec(env: &dyn Environment, opts: CanisterCallOpts) -> DfxResult { let timeout = expiry_duration(); + // amount has been validated by cycle_amount_validator + let cycles = opts + .with_cycles + .as_deref() + .map_or(0_u64, |amount| amount.parse::().unwrap()); + if is_query { - let blob = agent - .query(&canister_id, method_name) - .with_arg(&arg_value) - .call() - .await?; + let blob = match call_sender { + CallSender::SelectedId => { + agent + .query(&canister_id, method_name) + .with_arg(&arg_value) + .call() + .await? + } + CallSender::Wallet(wallet_id) | CallSender::SelectedIdWallet(wallet_id) => { + let wallet = Identity::build_wallet_canister(wallet_id.clone(), env)?; + do_wallet_call( + &wallet, + &CallIn { + canister: canister_id, + method_name: method_name.to_string(), + args: arg_value, + cycles, + }, + ) + .await? + } + }; print_idl_blob(&blob, output_type, &method_type) .context("Invalid data: Invalid IDL blob.")?; } else if opts.r#async { - let request_id = agent - .update(&canister_id, method_name) - .with_arg(&arg_value) - .call() - .await?; + let request_id = match call_sender { + CallSender::SelectedId => { + agent + .update(&canister_id, method_name) + .with_arg(&arg_value) + .call() + .await? + } + CallSender::Wallet(wallet_id) | CallSender::SelectedIdWallet(wallet_id) => { + let wallet = Identity::build_wallet_canister(wallet_id.clone(), env)?; + // This is overkill, wallet.call should accept a Principal parameter + // Why do we need to construct a Canister? + let canister = Canister::builder() + .with_agent(agent) + .with_canister_id(canister_id) + .build() + .map_err(DfxError::from)?; + let mut args = Argument::default(); + args.set_raw_arg(arg_value); + + request_id_via_wallet_call(&wallet, &canister, method_name, args, cycles).await? + } + }; eprint!("Request ID: "); println!("0x{}", String::from(request_id)); } else { - let blob = agent - .update(&canister_id, method_name) - .with_arg(&arg_value) - .expire_after(timeout) - .call_and_wait(waiter_with_exponential_backoff()) - .await?; + let blob = match call_sender { + CallSender::SelectedId => { + agent + .update(&canister_id, method_name) + .with_arg(&arg_value) + .expire_after(timeout) + .call_and_wait(waiter_with_exponential_backoff()) + .await? + } + CallSender::Wallet(wallet_id) | CallSender::SelectedIdWallet(wallet_id) => { + let wallet = Identity::build_wallet_canister(wallet_id.clone(), env)?; + do_wallet_call( + &wallet, + &CallIn { + canister: canister_id, + method_name: method_name.to_string(), + args: arg_value, + cycles, + }, + ) + .await? + } + }; print_idl_blob(&blob, output_type, &method_type) .context("Invalid data: Invalid IDL blob.")?; diff --git a/src/dfx/src/commands/canister/create.rs b/src/dfx/src/commands/canister/create.rs index 840e5d98ce..9f217fa57f 100644 --- a/src/dfx/src/commands/canister/create.rs +++ b/src/dfx/src/commands/canister/create.rs @@ -1,5 +1,6 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::identity::identity_utils::CallSender; use crate::lib::operations::canister::create_canister; use crate::lib::root_key::fetch_root_key_if_needed; use crate::util::clap::validators::cycle_amount_validator; @@ -26,19 +27,30 @@ pub struct CanisterCreateOpts { with_cycles: Option, } -pub async fn exec(env: &dyn Environment, opts: CanisterCreateOpts) -> DfxResult { +pub async fn exec( + env: &dyn Environment, + opts: CanisterCreateOpts, + call_sender: &CallSender, +) -> DfxResult { let config = env.get_config_or_anyhow()?; let timeout = expiry_duration(); fetch_root_key_if_needed(env).await?; let with_cycles = opts.with_cycles.as_deref(); if let Some(canister_name) = opts.canister_name.clone() { - create_canister(env, canister_name.as_str(), timeout, with_cycles).await + create_canister( + env, + canister_name.as_str(), + timeout, + with_cycles, + call_sender, + ) + .await } else if opts.all { // Create all canisters. if let Some(canisters) = &config.get_config().canisters { for canister_name in canisters.keys() { - create_canister(env, canister_name, timeout, with_cycles).await?; + create_canister(env, canister_name, timeout, with_cycles, call_sender).await?; } } Ok(()) diff --git a/src/dfx/src/commands/canister/delete.rs b/src/dfx/src/commands/canister/delete.rs index 2b0320bc86..4e0b195c92 100644 --- a/src/dfx/src/commands/canister/delete.rs +++ b/src/dfx/src/commands/canister/delete.rs @@ -1,5 +1,6 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::identity::identity_utils::CallSender; use crate::lib::models::canister_id_store::CanisterIdStore; use crate::lib::operations::canister; use crate::lib::root_key::fetch_root_key_if_needed; @@ -26,6 +27,7 @@ async fn delete_canister( env: &dyn Environment, canister_name: &str, timeout: Duration, + call_sender: &CallSender, ) -> DfxResult { let log = env.get_logger(); let mut canister_id_store = CanisterIdStore::for_env(env)?; @@ -37,25 +39,29 @@ async fn delete_canister( canister_id.to_text(), ); - canister::delete_canister(env, canister_id, timeout).await?; + canister::delete_canister(env, canister_id, timeout, &call_sender).await?; canister_id_store.remove(canister_name)?; Ok(()) } -pub async fn exec(env: &dyn Environment, opts: CanisterDeleteOpts) -> DfxResult { +pub async fn exec( + env: &dyn Environment, + opts: CanisterDeleteOpts, + call_sender: &CallSender, +) -> DfxResult { let config = env.get_config_or_anyhow()?; let timeout = expiry_duration(); fetch_root_key_if_needed(env).await?; if let Some(canister_name) = opts.canister_name.as_deref() { - delete_canister(env, canister_name, timeout).await + delete_canister(env, canister_name, timeout, call_sender).await } else if opts.all { if let Some(canisters) = &config.get_config().canisters { for canister_name in canisters.keys() { - delete_canister(env, canister_name, timeout).await?; + delete_canister(env, canister_name, timeout, call_sender).await?; } } Ok(()) diff --git a/src/dfx/src/commands/canister/install.rs b/src/dfx/src/commands/canister/install.rs index 10d54ec59a..7e56fff3c5 100644 --- a/src/dfx/src/commands/canister/install.rs +++ b/src/dfx/src/commands/canister/install.rs @@ -2,6 +2,7 @@ use crate::config::dfinity::ConfigInterface; use crate::lib::canister_info::CanisterInfo; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::identity::identity_utils::CallSender; use crate::lib::models::canister_id_store::CanisterIdStore; use crate::lib::operations::canister::install_canister; use crate::lib::root_key::fetch_root_key_if_needed; @@ -78,7 +79,11 @@ fn get_memory_allocation( })) } -pub async fn exec(env: &dyn Environment, opts: CanisterInstallOpts) -> DfxResult { +pub async fn exec( + env: &dyn Environment, + opts: CanisterInstallOpts, + call_sender: &CallSender, +) -> DfxResult { let config = env.get_config_or_anyhow()?; let agent = env .get_agent() @@ -121,6 +126,7 @@ pub async fn exec(env: &dyn Environment, opts: CanisterInstallOpts) -> DfxResult mode, memory_allocation, timeout, + call_sender, ) .await } else if opts.all { @@ -152,6 +158,7 @@ pub async fn exec(env: &dyn Environment, opts: CanisterInstallOpts) -> DfxResult mode, memory_allocation, timeout, + call_sender, ) .await?; } diff --git a/src/dfx/src/commands/canister/mod.rs b/src/dfx/src/commands/canister/mod.rs index 22572d6a57..c8fa427230 100644 --- a/src/dfx/src/commands/canister/mod.rs +++ b/src/dfx/src/commands/canister/mod.rs @@ -1,5 +1,6 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::identity::identity_utils::call_sender; use crate::lib::provider::create_agent_environment; use clap::Clap; @@ -27,6 +28,16 @@ pub struct CanisterOpts { #[clap(long)] network: Option, + /// Specify a wallet canister id to perform the call. + /// If none specified, defaults to use the selected Identity's wallet canister. + #[clap(long)] + wallet: Option, + + /// Performs the call with the user Identity as the Sender of messages. + /// Bypasses the Wallet canister. + #[clap(long, conflicts_with("wallet"))] + no_wallet: bool, + #[clap(subcommand)] subcmd: SubCommand, } @@ -49,17 +60,18 @@ pub fn exec(env: &dyn Environment, opts: CanisterOpts) -> DfxResult { let agent_env = create_agent_environment(env, opts.network.clone())?; let runtime = Runtime::new().expect("Unable to create a runtime"); runtime.block_on(async { + let call_sender = call_sender(&agent_env, &opts.wallet, opts.no_wallet).await?; match opts.subcmd { - SubCommand::Call(v) => call::exec(&agent_env, v).await, - SubCommand::Create(v) => create::exec(&agent_env, v).await, - SubCommand::Delete(v) => delete::exec(&agent_env, v).await, + SubCommand::Call(v) => call::exec(&agent_env, v, &call_sender).await, + SubCommand::Create(v) => create::exec(&agent_env, v, &call_sender).await, + SubCommand::Delete(v) => delete::exec(&agent_env, v, &call_sender).await, SubCommand::Id(v) => id::exec(&agent_env, v).await, - SubCommand::Install(v) => install::exec(&agent_env, v).await, + SubCommand::Install(v) => install::exec(&agent_env, v, &call_sender).await, SubCommand::RequestStatus(v) => request_status::exec(&agent_env, v).await, - SubCommand::SetController(v) => set_controller::exec(&agent_env, v).await, - SubCommand::Start(v) => start::exec(&agent_env, v).await, - SubCommand::Status(v) => status::exec(&agent_env, v).await, - SubCommand::Stop(v) => stop::exec(&agent_env, v).await, + SubCommand::SetController(v) => set_controller::exec(&agent_env, v, &call_sender).await, + SubCommand::Start(v) => start::exec(&agent_env, v, &call_sender).await, + SubCommand::Status(v) => status::exec(&agent_env, v, &call_sender).await, + SubCommand::Stop(v) => stop::exec(&agent_env, v, &call_sender).await, } }) } diff --git a/src/dfx/src/commands/canister/set_controller.rs b/src/dfx/src/commands/canister/set_controller.rs index 154c444802..936e89d0e6 100644 --- a/src/dfx/src/commands/canister/set_controller.rs +++ b/src/dfx/src/commands/canister/set_controller.rs @@ -1,6 +1,7 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::identity::identity_manager::IdentityManager; +use crate::lib::identity::identity_utils::CallSender; use crate::lib::models::canister_id_store::CanisterIdStore; use crate::lib::operations::canister::set_controller; use crate::lib::root_key::fetch_root_key_if_needed; @@ -22,7 +23,11 @@ pub struct SetControllerOpts { new_controller: String, } -pub async fn exec(env: &dyn Environment, opts: SetControllerOpts) -> DfxResult { +pub async fn exec( + env: &dyn Environment, + opts: SetControllerOpts, + call_sender: &CallSender, +) -> DfxResult { let timeout = expiry_duration(); fetch_root_key_if_needed(env).await?; @@ -43,7 +48,7 @@ pub async fn exec(env: &dyn Environment, opts: SetControllerOpts) -> DfxResult { } }; - set_controller(env, canister_id, controller_principal, timeout).await?; + set_controller(env, canister_id, controller_principal, timeout, call_sender).await?; println!( "Set {:?} as controller of {:?}.", diff --git a/src/dfx/src/commands/canister/start.rs b/src/dfx/src/commands/canister/start.rs index 5ed6540766..e22d46105f 100644 --- a/src/dfx/src/commands/canister/start.rs +++ b/src/dfx/src/commands/canister/start.rs @@ -1,5 +1,6 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::identity::identity_utils::CallSender; use crate::lib::models::canister_id_store::CanisterIdStore; use crate::lib::operations::canister; use crate::lib::root_key::fetch_root_key_if_needed; @@ -25,6 +26,7 @@ async fn start_canister( env: &dyn Environment, canister_name: &str, timeout: Duration, + call_sender: &CallSender, ) -> DfxResult { let log = env.get_logger(); let canister_id_store = CanisterIdStore::for_env(env)?; @@ -37,23 +39,27 @@ async fn start_canister( canister_id.to_text(), ); - canister::start_canister(env, canister_id, timeout).await?; + canister::start_canister(env, canister_id, timeout, &call_sender).await?; Ok(()) } -pub async fn exec(env: &dyn Environment, opts: CanisterStartOpts) -> DfxResult { +pub async fn exec( + env: &dyn Environment, + opts: CanisterStartOpts, + call_sender: &CallSender, +) -> DfxResult { let config = env.get_config_or_anyhow()?; fetch_root_key_if_needed(env).await?; let timeout = expiry_duration(); if let Some(canister_name) = opts.canister_name.as_deref() { - start_canister(env, &canister_name, timeout).await + start_canister(env, &canister_name, timeout, call_sender).await } else if opts.all { if let Some(canisters) = &config.get_config().canisters { for canister_name in canisters.keys() { - start_canister(env, &canister_name, timeout).await?; + start_canister(env, &canister_name, timeout, call_sender).await?; } } Ok(()) diff --git a/src/dfx/src/commands/canister/status.rs b/src/dfx/src/commands/canister/status.rs index 052a0dfedb..c98afd65d2 100644 --- a/src/dfx/src/commands/canister/status.rs +++ b/src/dfx/src/commands/canister/status.rs @@ -1,5 +1,6 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::identity::identity_utils::CallSender; use crate::lib::models::canister_id_store::CanisterIdStore; use crate::lib::operations::canister; use crate::lib::root_key::fetch_root_key_if_needed; @@ -26,29 +27,34 @@ async fn canister_status( env: &dyn Environment, canister_name: &str, timeout: Duration, + call_sender: &CallSender, ) -> DfxResult { let log = env.get_logger(); let canister_id_store = CanisterIdStore::for_env(env)?; let canister_id = canister_id_store.get(canister_name)?; - let status = canister::get_canister_status(env, canister_id, timeout).await?; + let status = canister::get_canister_status(env, canister_id, timeout, call_sender).await?; info!(log, "Canister {}'s status is {}.", canister_name, status); Ok(()) } -pub async fn exec(env: &dyn Environment, opts: CanisterStatusOpts) -> DfxResult { +pub async fn exec( + env: &dyn Environment, + opts: CanisterStatusOpts, + call_sender: &CallSender, +) -> DfxResult { let config = env.get_config_or_anyhow()?; fetch_root_key_if_needed(env).await?; let timeout = expiry_duration(); if let Some(canister_name) = opts.canister_name.as_deref() { - canister_status(env, &canister_name, timeout).await + canister_status(env, &canister_name, timeout, call_sender).await } else if opts.all { if let Some(canisters) = &config.get_config().canisters { for canister_name in canisters.keys() { - canister_status(env, &canister_name, timeout).await?; + canister_status(env, &canister_name, timeout, call_sender).await?; } } Ok(()) diff --git a/src/dfx/src/commands/canister/stop.rs b/src/dfx/src/commands/canister/stop.rs index 76a30f6fc3..69a887edc2 100644 --- a/src/dfx/src/commands/canister/stop.rs +++ b/src/dfx/src/commands/canister/stop.rs @@ -1,5 +1,6 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::identity::identity_utils::CallSender; use crate::lib::models::canister_id_store::CanisterIdStore; use crate::lib::operations::canister; use crate::lib::root_key::fetch_root_key_if_needed; @@ -22,7 +23,12 @@ pub struct CanisterStopOpts { all: bool, } -async fn stop_canister(env: &dyn Environment, canister_name: &str, timeout: Duration) -> DfxResult { +async fn stop_canister( + env: &dyn Environment, + canister_name: &str, + timeout: Duration, + call_sender: &CallSender, +) -> DfxResult { let log = env.get_logger(); let canister_id_store = CanisterIdStore::for_env(env)?; let canister_id = canister_id_store.get(canister_name)?; @@ -34,23 +40,27 @@ async fn stop_canister(env: &dyn Environment, canister_name: &str, timeout: Dura canister_id.to_text(), ); - canister::stop_canister(env, canister_id, timeout).await?; + canister::stop_canister(env, canister_id, timeout, call_sender).await?; Ok(()) } -pub async fn exec(env: &dyn Environment, opts: CanisterStopOpts) -> DfxResult { +pub async fn exec( + env: &dyn Environment, + opts: CanisterStopOpts, + call_sender: &CallSender, +) -> DfxResult { let config = env.get_config_or_anyhow()?; fetch_root_key_if_needed(env).await?; let timeout = expiry_duration(); if let Some(canister_name) = opts.canister_name.as_deref() { - stop_canister(env, &canister_name, timeout).await + stop_canister(env, &canister_name, timeout, call_sender).await } else if opts.all { if let Some(canisters) = &config.get_config().canisters { for canister_name in canisters.keys() { - stop_canister(env, &canister_name, timeout).await?; + stop_canister(env, &canister_name, timeout, call_sender).await?; } } Ok(()) diff --git a/src/dfx/src/commands/deploy.rs b/src/dfx/src/commands/deploy.rs index 711f9074fe..a11561d0a3 100644 --- a/src/dfx/src/commands/deploy.rs +++ b/src/dfx/src/commands/deploy.rs @@ -1,5 +1,6 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::identity::identity_utils::call_sender; use crate::lib::operations::canister::deploy_canisters; use crate::lib::provider::create_agent_environment; use crate::lib::root_key::fetch_root_key_if_needed; @@ -36,6 +37,16 @@ pub struct DeployOpts { /// This amount is deducted from the wallet's cycle balance. #[clap(long, validator(cycle_amount_validator))] with_cycles: Option, + + /// Specify a wallet canister id to perform the call. + /// If none specified, defaults to use the selected Identity's wallet canister. + #[clap(long)] + wallet: Option, + + /// Performs the call with the user Identity as the Sender of messages. + /// Bypasses the Wallet canister. + #[clap(long, conflicts_with("wallet"))] + no_wallet: bool, } pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult { @@ -48,6 +59,8 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult { let with_cycles = opts.with_cycles.as_deref(); let runtime = Runtime::new().expect("Unable to create a runtime"); + + let call_sender = runtime.block_on(call_sender(&env, &opts.wallet, opts.no_wallet))?; runtime.block_on(fetch_root_key_if_needed(&env))?; runtime.block_on(deploy_canisters( @@ -57,5 +70,6 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult { argument_type, timeout, with_cycles, + &call_sender, )) } diff --git a/src/dfx/src/lib/identity/identity_utils.rs b/src/dfx/src/lib/identity/identity_utils.rs new file mode 100644 index 0000000000..573b12428a --- /dev/null +++ b/src/dfx/src/lib/identity/identity_utils.rs @@ -0,0 +1,37 @@ +use crate::lib::environment::Environment; +use crate::lib::error::DfxResult; +use crate::lib::identity::Identity; + +use ic_types::principal::Principal; + +#[derive(Debug, PartialEq)] +pub enum CallSender { + SelectedId, + SelectedIdWallet(Principal), + Wallet(Principal), +} + +// Determine whether the selected Identity, the selected Identitys wallet, +// or the provided wallet canister ID should be the Sender of the call. +pub async fn call_sender( + env: &dyn Environment, + wallet: &Option, + no_wallet: bool, +) -> DfxResult { + let sender = if wallet.is_none() && !no_wallet { + let network = env + .get_network_descriptor() + .expect("No network descriptor."); + let identity_name = env.get_selected_identity().expect("No selected identity."); + let wallet = + Identity::get_or_create_wallet_canister(env, network, &identity_name, true).await?; + CallSender::SelectedIdWallet(wallet.canister_id_().clone()) + } else if let Some(id) = wallet { + CallSender::Wallet(Principal::from_text(&id)?) + } else if no_wallet { + CallSender::SelectedId + } else { + unreachable!() + }; + Ok(sender) +} diff --git a/src/dfx/src/lib/identity/mod.rs b/src/dfx/src/lib/identity/mod.rs index e3a97f483f..87db26dd17 100644 --- a/src/dfx/src/lib/identity/mod.rs +++ b/src/dfx/src/lib/identity/mod.rs @@ -7,6 +7,7 @@ use crate::lib::config::get_config_dfx_dir_path; use crate::lib::environment::Environment; use crate::lib::error::{DfxError, DfxResult, IdentityError}; use crate::lib::network::network_descriptor::NetworkDescriptor; +use crate::lib::root_key::fetch_root_key_if_needed; use crate::lib::waiter::waiter_with_timeout; use crate::util; @@ -25,6 +26,7 @@ use std::io::Read; use std::path::PathBuf; pub mod identity_manager; +pub mod identity_utils; use crate::util::expiry_duration; pub use identity_manager::{ HardwareIdentityConfiguration, IdentityConfiguration, IdentityCreationParameters, @@ -299,11 +301,11 @@ impl Identity { ) -> DfxResult { match Identity::wallet_canister_id(env, network, name) { Err(_) => { + fetch_root_key_if_needed(env).await?; let mgr = ManagementCanister::create( env.get_agent() .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?, ); - info!( env.get_logger(), "Creating a wallet canister on the {} network.", network.name @@ -377,12 +379,7 @@ impl Identity { match Identity::wallet_canister_id(env, network, name) { Err(_) => { if create { - Identity::create_wallet(env, network, name, None).await.map_err(|err| - anyhow!("Unable to create a wallet canister on {}:\n{}\nWallet canisters on {} may only be created by an administrator.\nPlease submit your Principal (\"dfx identity get-principal\") in the intake form to have one created for you.", - network.name, - err, - network.name,) - ) + Identity::create_wallet(env, network, name, None).await } else { Err(anyhow!( "Could not find wallet for \"{}\" on \"{}\" network.", @@ -395,7 +392,7 @@ impl Identity { } } - fn wallet_canister_id( + pub fn wallet_canister_id( env: &dyn Environment, network: &NetworkDescriptor, name: &str, @@ -435,7 +432,7 @@ impl Identity { .clone()) } - fn build_wallet_canister( + pub fn build_wallet_canister( id: Principal, env: &dyn Environment, ) -> DfxResult> { diff --git a/src/dfx/src/lib/operations/canister/create_canister.rs b/src/dfx/src/lib/operations/canister/create_canister.rs index 84ed153459..d43e7eddc6 100644 --- a/src/dfx/src/lib/operations/canister/create_canister.rs +++ b/src/dfx/src/lib/operations/canister/create_canister.rs @@ -1,5 +1,6 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::identity::identity_utils::CallSender; use crate::lib::identity::Identity; use crate::lib::models::canister_id_store::CanisterIdStore; use crate::lib::provider::get_network_context; @@ -17,6 +18,7 @@ pub async fn create_canister( canister_name: &str, timeout: Duration, with_cycles: Option<&str>, + call_sender: &CallSender, ) -> DfxResult { let log = env.get_logger(); info!(log, "Creating canister {:?}...", canister_name); @@ -49,36 +51,49 @@ pub async fn create_canister( .get_network_descriptor() .expect("No network descriptor."); - let identity_name = env - .get_selected_identity() - .expect("No selected identity.") - .to_string(); - - info!(log, "Creating the canister using the wallet canister..."); - let wallet = - Identity::get_or_create_wallet_canister(env, network, &identity_name, true).await?; - let cid = if network.is_ic { - // Provisional commands are whitelisted on production - let mgr = ManagementCanister::create( - env.get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?, - ); - let (create_result,): (ic_utils::interfaces::wallet::CreateResult,) = wallet - .call_forward(mgr.update_("create_canister").build(), 0)? - .call_and_wait(waiter_with_timeout(timeout)) - .await?; - create_result.canister_id - } else { - let cycles = match with_cycles { - None => 1000000000001_u64, - Some(amount) => amount.parse::()?, - }; - wallet - .wallet_create_canister(cycles, None) - .call_and_wait(waiter_with_timeout(timeout)) - .await? - .0 - .canister_id + let agent = env + .get_agent() + .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let mgr = ManagementCanister::create(agent); + let cid = match call_sender { + CallSender::SelectedId => { + if network.is_ic { + // Provisional commands are whitelisted on production + mgr.create_canister() + .call_and_wait(waiter_with_timeout(timeout)) + .await? + .0 + } else { + // amount has been validated by cycle_amount_validator + let cycles = with_cycles.and_then(|amount| amount.parse::().ok()); + mgr.provisional_create_canister_with_cycles(cycles) + .call_and_wait(waiter_with_timeout(timeout)) + .await? + .0 + } + } + CallSender::Wallet(wallet_id) | CallSender::SelectedIdWallet(wallet_id) => { + let wallet = Identity::build_wallet_canister(wallet_id.clone(), env)?; + if network.is_ic { + // Provisional commands are whitelisted on production + let (create_result,): (ic_utils::interfaces::wallet::CreateResult,) = + wallet + .call_forward(mgr.update_("create_canister").build(), 0)? + .call_and_wait(waiter_with_timeout(timeout)) + .await?; + create_result.canister_id + } else { + // amount has been validated by cycle_amount_validator + let cycles = with_cycles + .map_or(1000000000001_u64, |amount| amount.parse::().unwrap()); + wallet + .wallet_create_canister(cycles, None) + .call_and_wait(waiter_with_timeout(timeout)) + .await? + .0 + .canister_id + } + } }; let canister_id = cid.to_text(); diff --git a/src/dfx/src/lib/operations/canister/deploy_canisters.rs b/src/dfx/src/lib/operations/canister/deploy_canisters.rs index 486f465bb1..0d86e80d4d 100644 --- a/src/dfx/src/lib/operations/canister/deploy_canisters.rs +++ b/src/dfx/src/lib/operations/canister/deploy_canisters.rs @@ -3,6 +3,7 @@ use crate::lib::builders::BuildConfig; use crate::lib::canister_info::CanisterInfo; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::identity::identity_utils::CallSender; use crate::lib::models::canister::CanisterPool; use crate::lib::models::canister_id_store::CanisterIdStore; use crate::lib::operations::canister::create_canister; @@ -24,6 +25,7 @@ pub async fn deploy_canisters( argument_type: Option<&str>, timeout: Duration, with_cycles: Option<&str>, + call_sender: &CallSender, ) -> DfxResult { let log = env.get_logger(); @@ -45,6 +47,7 @@ pub async fn deploy_canisters( &initial_canister_id_store, timeout, with_cycles, + call_sender, ) .await?; @@ -58,6 +61,7 @@ pub async fn deploy_canisters( argument, argument_type, timeout, + call_sender, ) .await?; @@ -80,6 +84,7 @@ async fn register_canisters( canister_id_store: &CanisterIdStore, timeout: Duration, with_cycles: Option<&str>, + call_sender: &CallSender, ) -> DfxResult { let canisters_to_create = canister_names .iter() @@ -91,7 +96,7 @@ async fn register_canisters( } else { info!(env.get_logger(), "Creating canisters..."); for canister_name in &canisters_to_create { - create_canister(env, &canister_name, timeout, with_cycles).await?; + create_canister(env, &canister_name, timeout, with_cycles, &call_sender).await?; } } Ok(()) @@ -105,6 +110,7 @@ fn build_canisters(env: &dyn Environment, canister_names: &[String], config: &Co canister_pool.build_or_fail(BuildConfig::from_config(&config)?) } +#[allow(clippy::too_many_arguments)] async fn install_canisters( env: &dyn Environment, canister_names: &[String], @@ -113,6 +119,7 @@ async fn install_canisters( argument: Option<&str>, argument_type: Option<&str>, timeout: Duration, + call_sender: &CallSender, ) -> DfxResult { info!(env.get_logger(), "Installing canisters..."); @@ -161,6 +168,7 @@ async fn install_canisters( first_mode, memory_allocation, timeout, + &call_sender, ) .await; @@ -193,6 +201,7 @@ async fn install_canisters( second_mode, memory_allocation, timeout, + &call_sender, ) .await } diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index 95200e1b84..3e5cdb4296 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -1,7 +1,8 @@ use crate::lib::canister_info::CanisterInfo; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; -use crate::lib::identity::Identity as DfxIdentity; +use crate::lib::identity::identity_utils::CallSender; +use crate::lib::identity::Identity; use crate::lib::installers::assets::post_install_store_assets; use crate::lib::waiter::waiter_with_timeout; @@ -25,6 +26,7 @@ pub async fn install_canister( mode: InstallMode, memory_allocation: Option, timeout: Duration, + call_sender: &CallSender, ) -> DfxResult { let mgr = ManagementCanister::create(agent); let log = env.get_logger(); @@ -44,59 +46,83 @@ pub async fn install_canister( .expect("Cannot get WASM output path."); let wasm_module = std::fs::read(wasm_path)?; - let identity_name = env.get_selected_identity().expect("No selected identity."); - let network = env - .get_network_descriptor() - .expect("No network descriptor."); + match call_sender { + CallSender::SelectedId => { + let install_builder = mgr + .install_code(&canister_id, &wasm_module) + .with_raw_arg(args.to_vec()) + .with_mode(mode); + let install_builder = if let Some(ca) = compute_allocation { + install_builder.with_compute_allocation(ca) + } else { + install_builder + }; + let install_builder = if let Some(ma) = memory_allocation { + install_builder.with_memory_allocation(ma) + } else { + install_builder + }; + install_builder + .build()? + .call_and_wait(waiter_with_timeout(timeout)) + .await?; + } + CallSender::Wallet(wallet_id) | CallSender::SelectedIdWallet(wallet_id) => { + let wallet = Identity::build_wallet_canister(wallet_id.clone(), env)?; - #[derive(candid::CandidType)] - struct CanisterInstall { - mode: InstallMode, - canister_id: Principal, - wasm_module: Vec, - arg: Vec, - compute_allocation: Option, - memory_allocation: Option, - } - - let install_args = CanisterInstall { - mode, - canister_id: canister_id.clone(), - wasm_module, - arg: args.to_vec(), - compute_allocation: compute_allocation.map(|x| candid::Nat::from(u8::from(x))), - memory_allocation: memory_allocation.map(|x| candid::Nat::from(u64::from(x))), - }; - let wallet = DfxIdentity::get_wallet_canister(env, network, &identity_name).await?; + #[derive(candid::CandidType)] + struct CanisterInstall { + mode: InstallMode, + canister_id: Principal, + wasm_module: Vec, + arg: Vec, + compute_allocation: Option, + memory_allocation: Option, + } - wallet - .call_forward( - mgr.update_("install_code").with_arg(install_args).build(), - 0, - )? - .call_and_wait(waiter_with_timeout(timeout)) - .await?; + let install_args = CanisterInstall { + mode, + canister_id: canister_id.clone(), + wasm_module, + arg: args.to_vec(), + compute_allocation: compute_allocation.map(|x| candid::Nat::from(u8::from(x))), + memory_allocation: memory_allocation.map(|x| candid::Nat::from(u64::from(x))), + }; + wallet + .call_forward( + mgr.update_("install_code").with_arg(install_args).build(), + 0, + )? + .call_and_wait(waiter_with_timeout(timeout)) + .await?; + } + } if canister_info.get_type() == "assets" { - let wallet = DfxIdentity::get_wallet_canister(env, network, &identity_name).await?; - let self_id = env - .get_selected_identity_principal() - .expect("Selected identity not instantiated."); - info!( - log, - "Authorizing our identity ({}) to the asset canister...", identity_name - ); - let canister = Canister::builder() - .with_agent(agent) - .with_canister_id(canister_id.clone()) - .build() - .unwrap(); - - // Before storing assets, make sure the DFX principal is in there first. - wallet - .call_forward(canister.update_("authorize").with_arg(self_id).build(), 0)? - .call_and_wait(waiter_with_timeout(timeout)) - .await?; + match call_sender { + CallSender::Wallet(wallet_id) | CallSender::SelectedIdWallet(wallet_id) => { + let wallet = Identity::build_wallet_canister(wallet_id.clone(), env)?; + let identity_name = env.get_selected_identity().expect("No selected identity."); + info!( + log, + "Authorizing our identity ({}) to the asset canister...", identity_name + ); + let canister = Canister::builder() + .with_agent(agent) + .with_canister_id(canister_id.clone()) + .build() + .unwrap(); + let self_id = env + .get_selected_identity_principal() + .expect("Selected identity not instantiated."); + // Before storing assets, make sure the DFX principal is in there first. + wallet + .call_forward(canister.update_("authorize").with_arg(self_id).build(), 0)? + .call_and_wait(waiter_with_timeout(timeout)) + .await?; + } + _ => (), + }; info!(log, "Uploading assets to asset canister..."); post_install_store_assets(&canister_info, &agent, timeout).await?; diff --git a/src/dfx/src/lib/operations/canister/mod.rs b/src/dfx/src/lib/operations/canister/mod.rs index 158f12b008..2b5db78a10 100644 --- a/src/dfx/src/lib/operations/canister/mod.rs +++ b/src/dfx/src/lib/operations/canister/mod.rs @@ -8,6 +8,7 @@ pub use install_canister::install_canister; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::identity::identity_utils::CallSender; use crate::lib::identity::Identity; use crate::lib::waiter::waiter_with_timeout; use anyhow::anyhow; @@ -20,11 +21,12 @@ use ic_utils::interfaces::ManagementCanister; use serde::Deserialize; use std::time::Duration; -async fn do_wallet_management_call( +async fn do_management_call( env: &dyn Environment, method: &str, arg: A, timeout: Duration, + call_sender: &CallSender, ) -> DfxResult where A: CandidType + Sync + Send, @@ -35,17 +37,23 @@ where .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; let mgr = ManagementCanister::create(agent); - // Get the wallet canister. - let network = env - .get_network_descriptor() - .expect("No network descriptor."); - let identity_name = env.get_selected_identity().expect("No selected identity."); - let wallet = Identity::get_wallet_canister(env, network, &identity_name).await?; - - let out: O = wallet - .call_forward(mgr.update_(method).with_arg(arg).build(), 0)? - .call_and_wait(waiter_with_timeout(timeout)) - .await?; + let out = match call_sender { + CallSender::SelectedId => { + mgr.update_(method) + .with_arg(arg) + .build() + .call_and_wait(waiter_with_timeout(timeout)) + .await? + } + CallSender::Wallet(wallet_id) | CallSender::SelectedIdWallet(wallet_id) => { + let wallet = Identity::build_wallet_canister(wallet_id.clone(), env)?; + let out: O = wallet + .call_forward(mgr.update_(method).with_arg(arg).build(), 0)? + .call_and_wait(waiter_with_timeout(timeout)) + .await?; + out + } + }; Ok(out) } @@ -54,6 +62,7 @@ pub async fn get_canister_status( env: &dyn Environment, canister_id: Principal, timeout: Duration, + call_sender: &CallSender, ) -> DfxResult { #[derive(CandidType)] struct In { @@ -65,8 +74,14 @@ pub async fn get_canister_status( status: CanisterStatus, } - let (out,): (Out,) = - do_wallet_management_call(env, "canister_status", In { canister_id }, timeout).await?; + let (out,): (Out,) = do_management_call( + env, + "canister_status", + In { canister_id }, + timeout, + call_sender, + ) + .await?; Ok(out.status) } @@ -74,14 +89,21 @@ pub async fn start_canister( env: &dyn Environment, canister_id: Principal, timeout: Duration, + call_sender: &CallSender, ) -> DfxResult { #[derive(CandidType)] struct In { canister_id: Principal, } - let _: () = - do_wallet_management_call(env, "start_canister", In { canister_id }, timeout).await?; + let _: () = do_management_call( + env, + "start_canister", + In { canister_id }, + timeout, + call_sender, + ) + .await?; Ok(()) } @@ -89,14 +111,21 @@ pub async fn stop_canister( env: &dyn Environment, canister_id: Principal, timeout: Duration, + call_sender: &CallSender, ) -> DfxResult { #[derive(CandidType)] struct In { canister_id: Principal, } - let _: () = - do_wallet_management_call(env, "stop_canister", In { canister_id }, timeout).await?; + let _: () = do_management_call( + env, + "stop_canister", + In { canister_id }, + timeout, + call_sender, + ) + .await?; Ok(()) } @@ -105,6 +134,7 @@ pub async fn set_controller( canister_id: Principal, new_controller: Principal, timeout: Duration, + call_sender: &CallSender, ) -> DfxResult { #[derive(CandidType)] struct In { @@ -112,7 +142,7 @@ pub async fn set_controller( new_controller: Principal, } - let _: () = do_wallet_management_call( + let _: () = do_management_call( env, "set_controller", In { @@ -120,6 +150,7 @@ pub async fn set_controller( new_controller, }, timeout, + call_sender, ) .await?; Ok(()) @@ -129,13 +160,20 @@ pub async fn delete_canister( env: &dyn Environment, canister_id: Principal, timeout: Duration, + call_sender: &CallSender, ) -> DfxResult { #[derive(CandidType)] struct In { canister_id: Principal, } - let _: () = - do_wallet_management_call(env, "delete_canister", In { canister_id }, timeout).await?; + let _: () = do_management_call( + env, + "delete_canister", + In { canister_id }, + timeout, + call_sender, + ) + .await?; Ok(()) }