diff --git a/Cargo.lock b/Cargo.lock index 63686572d7..fe58c7285a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1885,8 +1885,8 @@ dependencies = [ [[package]] name = "ic-agent" -version = "0.2.0" -source = "git+https://github.com/dfinity/agent-rs.git?branch=next#5472fb610faa6cc1f0c12c82a745e435603fae08" +version = "0.3.0" +source = "git+https://github.com/dfinity/agent-rs.git?branch=next#d0a17fcb64d658b782fd5e5158e748047403964a" dependencies = [ "async-trait", "base32", @@ -1898,7 +1898,6 @@ dependencies = [ "ic-types", "leb128", "mime", - "num-bigint 0.3.2", "openssl", "pem 0.8.3", "rand 0.7.3", @@ -1916,8 +1915,8 @@ dependencies = [ [[package]] name = "ic-identity-hsm" -version = "0.2.0" -source = "git+https://github.com/dfinity/agent-rs.git?branch=next#5472fb610faa6cc1f0c12c82a745e435603fae08" +version = "0.2.1" +source = "git+https://github.com/dfinity/agent-rs.git?branch=next#d0a17fcb64d658b782fd5e5158e748047403964a" dependencies = [ "hex", "ic-agent", @@ -1932,7 +1931,7 @@ dependencies = [ [[package]] name = "ic-types" version = "0.1.2" -source = "git+https://github.com/dfinity/agent-rs.git?branch=next#5472fb610faa6cc1f0c12c82a745e435603fae08" +source = "git+https://github.com/dfinity/agent-rs.git?branch=next#d0a17fcb64d658b782fd5e5158e748047403964a" dependencies = [ "base32", "crc32fast", @@ -1943,8 +1942,8 @@ dependencies = [ [[package]] name = "ic-utils" -version = "0.2.0" -source = "git+https://github.com/dfinity/agent-rs.git?branch=next#5472fb610faa6cc1f0c12c82a745e435603fae08" +version = "0.2.1" +source = "git+https://github.com/dfinity/agent-rs.git?branch=next#d0a17fcb64d658b782fd5e5158e748047403964a" dependencies = [ "async-trait", "candid", diff --git a/Cargo.toml b/Cargo.toml index c372a1dc72..57dffe78df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,25 +4,25 @@ members = [ ] [patch.crates-io.ic-agent] -version = "0.2.0" +version = "0.3.0" git = "https://github.com/dfinity/agent-rs.git" branch = "next" -rev = "5472fb610faa6cc1f0c12c82a745e435603fae08" +rev = "d0a17fcb64d658b782fd5e5158e748047403964a" [patch.crates-io.ic-identity-hsm] -version = "0.2.0" +version = "0.2.1" git = "https://github.com/dfinity/agent-rs.git" branch = "next" -rev = "5472fb610faa6cc1f0c12c82a745e435603fae08" +rev = "d0a17fcb64d658b782fd5e5158e748047403964a" [patch.crates-io.ic-types] version = "0.1.2" git = "https://github.com/dfinity/agent-rs.git" branch = "next" -rev = "5472fb610faa6cc1f0c12c82a745e435603fae08" +rev = "d0a17fcb64d658b782fd5e5158e748047403964a" [patch.crates-io.ic-utils] -version = "0.2.0" +version = "0.2.1" git = "https://github.com/dfinity/agent-rs.git" branch = "next" -rev = "5472fb610faa6cc1f0c12c82a745e435603fae08" +rev = "d0a17fcb64d658b782fd5e5158e748047403964a" diff --git a/e2e/tests-dfx/assetscanister.bash b/e2e/tests-dfx/assetscanister.bash index f90e3c1a46..339fc214b5 100644 --- a/e2e/tests-dfx/assetscanister.bash +++ b/e2e/tests-dfx/assetscanister.bash @@ -243,7 +243,7 @@ CHERRIES" "$stdout" dfx_start dfx canister --no-wallet create --all dfx build - dfx canister --no-wallet install --memory-allocation 15mb e2e_project_assets + dfx canister --no-wallet install e2e_project_assets # retrieve() refuses to serve just part of an asset assert_command_fail dfx canister call --query e2e_project_assets retrieve '("/large-asset.bin")' diff --git a/e2e/tests-dfx/controller.bash b/e2e/tests-dfx/update_settings.bash similarity index 65% rename from e2e/tests-dfx/controller.bash rename to e2e/tests-dfx/update_settings.bash index 22ab1ce8a1..8cde8286b9 100644 --- a/e2e/tests-dfx/controller.bash +++ b/e2e/tests-dfx/update_settings.bash @@ -36,8 +36,8 @@ teardown() { ID=$(dfx canister id hello) # Set controller using canister name and identity name - assert_command dfx canister set-controller hello "${BOB_WALLET}" - assert_match "Set \"${BOB_WALLET}\" as controller of \"hello\"." + assert_command dfx canister update-settings hello --controller "${BOB_WALLET}" + assert_match "Updated \"${BOB_WALLET}\" as controller of \"hello\"." # Juana is controller, Jose cannot reinstall assert_command_fail dfx canister install hello -m reinstall @@ -47,22 +47,22 @@ teardown() { assert_command dfx identity use bob # Set controller using canister id and principal - assert_command dfx canister set-controller "${ID}" "${ALICE_WALLET}" - assert_match "Set \"${ALICE_WALLET}\" as controller of \"${ID}\"." + assert_command dfx canister update-settings "${ID}" --controller "${ALICE_WALLET}" + assert_match "Updated \"${ALICE_WALLET}\" as controller of \"${ID}\"." assert_command_fail dfx canister install hello -m reinstall # Set controller using combination of name/id and identity/principal - assert_command dfx --identity alice canister set-controller hello "${BOB_WALLET}" - assert_match "Set \"${BOB_WALLET}\" as controller of \"hello\"." + assert_command dfx --identity alice canister update-settings hello --controller "${BOB_WALLET}" + assert_match "Updated \"${BOB_WALLET}\" as controller of \"hello\"." - assert_command dfx --identity bob canister set-controller "${ID}" alice - assert_match "Set \"alice\" as controller of \"${ID}\"." + assert_command dfx --identity bob canister update-settings "${ID}" --controller alice + assert_match "Updated \"alice\" as controller of \"${ID}\"." # Set controller using invalid principal/identity fails - assert_command_fail dfx --identity alice canister set-controller hello charlie + assert_command_fail dfx --identity alice canister --no-wallet update-settings hello --controller charlie assert_match "Identity charlie does not exist" # Set controller using invalid canister name/id fails - assert_command_fail dfx --identity alice canister set-controller hello_assets bob + assert_command_fail dfx --identity alice canister --no-wallet update-settings hello_assets --controller bob assert_match "Cannot find canister id. Please issue 'dfx canister create hello_assets'." -} +} \ No newline at end of file diff --git a/e2e/tests-dfx/wallet.bash b/e2e/tests-dfx/wallet.bash index 1205a18c33..da1bd0da14 100644 --- a/e2e/tests-dfx/wallet.bash +++ b/e2e/tests-dfx/wallet.bash @@ -24,14 +24,14 @@ teardown() { setup_actuallylocal_network # get Canister IDs to install the wasm onto - dfx canister --network actuallylocal create dummy_canister1 - ID=$(dfx canister --network actuallylocal id dummy_canister1) - dfx canister --network actuallylocal create dummy_canister2 - ID_TWO=$(dfx canister --network actuallylocal id dummy_canister2) + dfx canister --network actuallylocal create hello + ID=$(dfx canister --network actuallylocal id hello) + dfx canister --network actuallylocal create hello_assets + ID_TWO=$(dfx canister --network actuallylocal id hello_assets) # set controller to user - dfx canister --network actuallylocal set-controller dummy_canister1 "$(dfx identity get-principal)" - dfx canister --network actuallylocal set-controller dummy_canister2 "$(dfx identity get-principal)" + dfx canister --network actuallylocal update-settings hello --controller "$(dfx identity get-principal)" + dfx canister --network actuallylocal update-settings hello_assets --controller "$(dfx identity get-principal)" # We're testing on a local network so the create command actually creates a wallet # Delete this file to force associate wallet created by deploy-wallet to identity @@ -49,7 +49,7 @@ teardown() { dfx_new dfx_start WALLET_ID=$(dfx identity get-wallet) - CREATE_RES=$(dfx canister --no-wallet call "${WALLET_ID}" wallet_create_wallet "(record { cycles = (2000000000000:nat64); controller = opt principal \"$(dfx identity get-principal)\";})") + CREATE_RES=$(dfx canister --no-wallet call "${WALLET_ID}" wallet_create_wallet "(record { cycles = (2000000000000:nat64); settings = record {controller = opt principal \"$(dfx identity get-principal)\";};})") CHILD_ID=$(echo "${CREATE_RES}" | tr '\n' ' ' | cut -d'"' -f 2) assert_command dfx canister --no-wallet call "${CHILD_ID}" wallet_balance '()' } diff --git a/nix/sources.json b/nix/sources.json index 359651dd63..8981b1153e 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -34,13 +34,13 @@ "dfinity": { "branch": "master", "repo": "ssh://git@github.com/dfinity-lab/dfinity", - "rev": "8967aa9f7435ea7eab47f4d9e1c5098ae4cc974c", + "rev": "6c7e2dd6f85b655809947b618c7d343429c34438", "type": "git" }, "ic-ref": { "repo": "ssh://git@github.com/dfinity-lab/ic-ref", - "rev": "215641629ace2d590013f89bb3f3fe3b0a0d3c3b", - "tag": "0.16.0", + "rev": "a858395300d67685545e945dd4afd4b6af26c1a6", + "tag": "0.17.0", "type": "git" }, "motoko": { diff --git a/src/dfx/Cargo.toml b/src/dfx/Cargo.toml index 74cc63e92d..4ccc4c3b22 100644 --- a/src/dfx/Cargo.toml +++ b/src/dfx/Cargo.toml @@ -73,29 +73,29 @@ wasmparser = "0.45.0" webpki-roots = "0.21.0" [dependencies.ic-agent] -version = "0.2.0" +version = "0.3.0" git = "https://github.com/dfinity/agent-rs.git" branch = "next" -rev = "5472fb610faa6cc1f0c12c82a745e435603fae08" +rev = "d0a17fcb64d658b782fd5e5158e748047403964a" features = ["reqwest"] [dependencies.ic-identity-hsm] -version = "0.2.0" +version = "0.2.1" git = "https://github.com/dfinity/agent-rs.git" branch = "next" -rev = "5472fb610faa6cc1f0c12c82a745e435603fae08" +rev = "d0a17fcb64d658b782fd5e5158e748047403964a" [dependencies.ic-types] version = "0.1.2" git = "https://github.com/dfinity/agent-rs.git" branch = "next" -rev = "5472fb610faa6cc1f0c12c82a745e435603fae08" +rev = "d0a17fcb64d658b782fd5e5158e748047403964a" [dependencies.ic-utils] -version = "0.2.0" +version = "0.2.1" git = "https://github.com/dfinity/agent-rs.git" branch = "next" -rev = "5472fb610faa6cc1f0c12c82a745e435603fae08" +rev = "d0a17fcb64d658b782fd5e5158e748047403964a" [dev-dependencies] env_logger = "0.6" diff --git a/src/dfx/src/commands/canister/call.rs b/src/dfx/src/commands/canister/call.rs index 06d00e53e1..c5b9033b1a 100644 --- a/src/dfx/src/commands/canister/call.rs +++ b/src/dfx/src/commands/canister/call.rs @@ -14,7 +14,8 @@ use candid::{CandidType, Decode, Deserialize}; use clap::{ArgSettings, Clap}; use ic_types::principal::Principal as CanisterId; use ic_utils::canister::{Argument, Canister}; -use ic_utils::interfaces::management_canister::{CanisterInstall, MgmtMethod}; +use ic_utils::interfaces::management_canister::builders::{CanisterInstall, CanisterSettings}; +use ic_utils::interfaces::management_canister::MgmtMethod; use ic_utils::interfaces::wallet::{CallForwarder, CallResult}; use ic_utils::interfaces::Wallet; use std::option::Option; @@ -123,11 +124,11 @@ pub fn get_effective_canister_id( let install_args = candid::Decode!(arg_value, CanisterInstall)?; Ok(install_args.canister_id) } - MgmtMethod::SetController => { + MgmtMethod::UpdateSettings => { #[derive(CandidType, Deserialize)] struct In { canister_id: CanisterId, - new_controller: CanisterId, + settings: CanisterSettings, } let in_args = candid::Decode!(arg_value, In)?; Ok(in_args.canister_id) @@ -137,6 +138,7 @@ pub fn get_effective_canister_id( | MgmtMethod::CanisterStatus | MgmtMethod::DeleteCanister | MgmtMethod::DepositCycles + | MgmtMethod::UninstallCode | MgmtMethod::ProvisionalTopUpCanister => { #[derive(CandidType, Deserialize)] struct In { diff --git a/src/dfx/src/commands/canister/create.rs b/src/dfx/src/commands/canister/create.rs index 9f217fa57f..2209190c72 100644 --- a/src/dfx/src/commands/canister/create.rs +++ b/src/dfx/src/commands/canister/create.rs @@ -1,13 +1,22 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::ic_attributes::{ + get_compute_allocation, get_freezing_threshold, get_memory_allocation, CanisterSettings, +}; +use crate::lib::identity::identity_manager::IdentityManager; 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; +use crate::util::clap::validators::{ + compute_allocation_validator, freezing_threshold_validator, memory_allocation_validator, +}; use crate::util::expiry_duration; -use anyhow::bail; -use clap::Clap; +use anyhow::{anyhow, bail}; +use clap::{ArgSettings, Clap}; +use ic_agent::identity::Identity; +use ic_types::principal::Principal as CanisterId; /// Creates an empty canister on the Internet Computer and /// associates the Internet Computer assigned Canister ID to the canister name. @@ -25,6 +34,21 @@ pub struct CanisterCreateOpts { /// This amount is deducted from the wallet's cycle balance. #[clap(long, validator(cycle_amount_validator))] with_cycles: Option, + + /// Specifies the identity name or the principal of the new controller. + controller: Option, + + /// Specifies the canister's compute allocation. This should be a percent in the range [0..100] + #[clap(long, short('c'), validator(compute_allocation_validator))] + compute_allocation: Option, + + /// Specifies how much memory the canister is allowed to use in total. + /// This should be a value in the range [0..256 TB] + #[clap(long, validator(memory_allocation_validator))] + memory_allocation: Option, + + #[clap(long, validator(freezing_threshold_validator), setting = ArgSettings::Hidden)] + freezing_threshold: Option, } pub async fn exec( @@ -36,21 +60,94 @@ pub async fn exec( 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() { + + let config_interface = config.get_config(); + + let controller = if let Some(controller) = opts.controller.clone() { + match CanisterId::from_text(controller.clone()) { + Ok(principal) => Some(principal), + Err(_) => { + let current_id = env.get_selected_identity().unwrap(); + if current_id == &controller { + Some(env.get_selected_identity_principal().unwrap()) + } else { + let identity_name = &controller; + let sender = IdentityManager::new(env)? + .instantiate_identity_from_name(&identity_name.clone())?; + Some(sender.sender().map_err(|err| anyhow!(err))?) + } + } + } + } else { + None + }; + + if let Some(canister_name) = opts.canister_name.as_deref() { + let compute_allocation = get_compute_allocation( + opts.compute_allocation.clone(), + config_interface, + canister_name, + )?; + let memory_allocation = get_memory_allocation( + opts.memory_allocation.clone(), + config_interface, + canister_name, + )?; + let freezing_threshold = get_freezing_threshold( + opts.freezing_threshold.clone(), + config_interface, + canister_name, + )?; create_canister( env, - canister_name.as_str(), + canister_name, timeout, with_cycles, call_sender, + CanisterSettings { + controller, + compute_allocation, + memory_allocation, + freezing_threshold, + }, ) - .await + .await?; + Ok(()) } 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, call_sender).await?; + let compute_allocation = get_compute_allocation( + opts.compute_allocation.clone(), + config_interface, + canister_name, + )?; + let memory_allocation = get_memory_allocation( + opts.memory_allocation.clone(), + config_interface, + canister_name, + )?; + let freezing_threshold = get_freezing_threshold( + opts.freezing_threshold.clone(), + config_interface, + canister_name, + )?; + create_canister( + env, + canister_name, + timeout, + with_cycles, + call_sender, + CanisterSettings { + controller: controller.clone(), + compute_allocation, + memory_allocation, + freezing_threshold, + }, + ) + .await?; } } Ok(()) diff --git a/src/dfx/src/commands/canister/install.rs b/src/dfx/src/commands/canister/install.rs index 7e56fff3c5..827f60cd45 100644 --- a/src/dfx/src/commands/canister/install.rs +++ b/src/dfx/src/commands/canister/install.rs @@ -1,4 +1,3 @@ -use crate::config::dfinity::ConfigInterface; use crate::lib::canister_info::CanisterInfo; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; @@ -6,14 +5,11 @@ 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; -use crate::util::clap::validators::{compute_allocation_validator, memory_allocation_validator}; use crate::util::{blob_from_arguments, expiry_duration, get_candid_init_type}; use anyhow::{anyhow, bail}; use clap::Clap; -use humanize_rs::bytes::Bytes; -use ic_utils::interfaces::management_canister::{ComputeAllocation, InstallMode, MemoryAllocation}; -use std::convert::TryFrom; +use ic_utils::interfaces::management_canister::builders::InstallMode; use std::str::FromStr; /// Deploys compiled code as a canister on the Internet Computer. @@ -42,41 +38,6 @@ pub struct CanisterInstallOpts { /// Specifies the data type for the argument when making the call using an argument. #[clap(long, requires("argument"), possible_values(&["idl", "raw"]))] argument_type: Option, - - /// Specifies the canister's compute allocation. This should be a percent in the range [0..100] - #[clap(long, short('c'), validator(compute_allocation_validator))] - compute_allocation: Option, - - /// Specifies how much memory the canister is allowed to use in total. - /// This should be a value in the range [0..256 TB] - #[clap(long, validator(memory_allocation_validator))] - memory_allocation: Option, -} - -fn get_compute_allocation( - compute_allocation: Option, - config_interface: &ConfigInterface, - canister_name: &str, -) -> DfxResult> { - Ok(compute_allocation - .or(config_interface.get_compute_allocation(canister_name)?) - .map(|arg| { - ComputeAllocation::try_from(arg.parse::().unwrap()) - .expect("Compute Allocation must be a percentage.") - })) -} - -fn get_memory_allocation( - memory_allocation: Option, - config_interface: &ConfigInterface, - canister_name: &str, -) -> DfxResult> { - Ok(memory_allocation - .or(config_interface.get_memory_allocation(canister_name)?) - .map(|arg| { - MemoryAllocation::try_from(u64::try_from(arg.parse::().unwrap().size()).unwrap()) - .expect("Memory allocation must be between 0 and 2^48 (i.e 256TB), inclusively.") - })) } pub async fn exec( @@ -92,7 +53,6 @@ pub async fn exec( fetch_root_key_if_needed(env).await?; - let config_interface = config.get_config(); let mode = InstallMode::from_str(opts.mode.as_str()).map_err(|err| anyhow!(err))?; let canister_id_store = CanisterIdStore::for_env(env)?; @@ -106,25 +66,12 @@ pub async fn exec( let arg_type = opts.argument_type.as_deref(); let install_args = blob_from_arguments(arguments, None, arg_type, &init_type)?; - let compute_allocation = get_compute_allocation( - opts.compute_allocation.clone(), - config_interface, - canister_name, - )?; - let memory_allocation = get_memory_allocation( - opts.memory_allocation.clone(), - config_interface, - canister_name, - )?; - install_canister( env, &agent, &canister_info, &install_args, - compute_allocation, mode, - memory_allocation, timeout, call_sender, ) @@ -138,25 +85,12 @@ pub async fn exec( let install_args = []; - let compute_allocation = get_compute_allocation( - opts.compute_allocation.clone(), - config_interface, - canister_name, - )?; - let memory_allocation = get_memory_allocation( - opts.memory_allocation.clone(), - config_interface, - canister_name, - )?; - install_canister( env, &agent, &canister_info, &install_args, - compute_allocation, mode, - memory_allocation, timeout, call_sender, ) diff --git a/src/dfx/src/commands/canister/mod.rs b/src/dfx/src/commands/canister/mod.rs index 7f0e8fa0eb..de3457c7d6 100644 --- a/src/dfx/src/commands/canister/mod.rs +++ b/src/dfx/src/commands/canister/mod.rs @@ -14,11 +14,11 @@ mod info; mod install; mod request_status; mod send; -mod set_controller; mod sign; mod start; mod status; mod stop; +mod update_settings; /// Manages canisters deployed on a network replica. #[derive(Clap)] @@ -55,11 +55,11 @@ enum SubCommand { Install(install::CanisterInstallOpts), RequestStatus(request_status::RequestStatusOpts), Send(send::CanisterSendOpts), - SetController(set_controller::SetControllerOpts), Sign(sign::CanisterSignOpts), Start(start::CanisterStartOpts), Status(status::CanisterStatusOpts), Stop(stop::CanisterStopOpts), + UpdateSettings(update_settings::UpdateSettingsOpts), } pub fn exec(env: &dyn Environment, opts: CanisterOpts) -> DfxResult { @@ -76,11 +76,13 @@ pub fn exec(env: &dyn Environment, opts: CanisterOpts) -> DfxResult { SubCommand::Info(v) => info::exec(&agent_env, v).await, SubCommand::RequestStatus(v) => request_status::exec(&agent_env, v).await, SubCommand::Send(v) => send::exec(&agent_env, v, &call_sender).await, - SubCommand::SetController(v) => set_controller::exec(&agent_env, v, &call_sender).await, SubCommand::Sign(v) => sign::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, + SubCommand::UpdateSettings(v) => { + update_settings::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 deleted file mode 100644 index 936e89d0e6..0000000000 --- a/src/dfx/src/commands/canister/set_controller.rs +++ /dev/null @@ -1,58 +0,0 @@ -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; -use crate::util::expiry_duration; - -use anyhow::anyhow; -use clap::Clap; -use ic_agent::identity::Identity; -use ic_types::principal::Principal as CanisterId; - -/// Sets the provided identity's name or its principal as the -/// new controller of a canister on the Internet Computer network. -#[derive(Clap)] -pub struct SetControllerOpts { - /// Specifies the canister name or the canister identifier for the canister to be controlled. - canister: String, - - /// Specifies the identity name or the principal of the new controller. - new_controller: String, -} - -pub async fn exec( - env: &dyn Environment, - opts: SetControllerOpts, - call_sender: &CallSender, -) -> DfxResult { - let timeout = expiry_duration(); - fetch_root_key_if_needed(env).await?; - - let canister_id = match CanisterId::from_text(&opts.canister) { - Ok(id) => id, - Err(_) => CanisterIdStore::for_env(env)?.get(&opts.canister)?, - }; - - let controller_principal = match CanisterId::from_text(&opts.new_controller) { - Ok(principal) => principal, - Err(_) => { - // If this is not a textual principal format, use the wallet of the person - // and not its principal directly. - let identity_name = &opts.new_controller; - let sender = IdentityManager::new(env)? - .instantiate_identity_from_name(&identity_name.clone())?; - sender.sender().map_err(|err| anyhow!(err))? - } - }; - - set_controller(env, canister_id, controller_principal, timeout, call_sender).await?; - - println!( - "Set {:?} as controller of {:?}.", - opts.new_controller, opts.canister - ); - Ok(()) -} diff --git a/src/dfx/src/commands/canister/status.rs b/src/dfx/src/commands/canister/status.rs index 3bdd19b352..37a72ad691 100644 --- a/src/dfx/src/commands/canister/status.rs +++ b/src/dfx/src/commands/canister/status.rs @@ -35,10 +35,13 @@ async fn canister_status( let status = canister::get_canister_status(env, canister_id, timeout, call_sender).await?; - info!(log, "Canister status call result for {}.\nStatus: {}\nController: {}\nMemory Size: {:?}\nBalance: {} Cycles\nModule hash: {}", + info!(log, "Canister status call result for {}.\nStatus: {}\nController: {}\nMemory allocation: {}\nCompute allocation: {}\nFreezing threshold: {}\nMemory Size: {:?}\nBalance: {} Cycles\nModule hash: {}", canister_name, status.status, - status.controller.to_text(), + status.settings.controller, + status.settings.memory_allocation, + status.settings.compute_allocation, + status.settings.freezing_threshold, status.memory_size, status.cycles, status.module_hash.map_or_else(|| "None".to_string(), |v| format!("0x{}", hex::encode(v))) diff --git a/src/dfx/src/commands/canister/update_settings.rs b/src/dfx/src/commands/canister/update_settings.rs new file mode 100644 index 0000000000..66d0c7143f --- /dev/null +++ b/src/dfx/src/commands/canister/update_settings.rs @@ -0,0 +1,169 @@ +use crate::lib::environment::Environment; +use crate::lib::error::DfxResult; +use crate::lib::ic_attributes::{ + get_compute_allocation, get_freezing_threshold, get_memory_allocation, CanisterSettings, +}; +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::update_settings; +use crate::lib::root_key::fetch_root_key_if_needed; +use crate::util::clap::validators::{ + compute_allocation_validator, freezing_threshold_validator, memory_allocation_validator, +}; +use crate::util::expiry_duration; + +use anyhow::{anyhow, bail}; +use clap::{ArgSettings, Clap}; +use ic_agent::identity::Identity; +use ic_types::principal::Principal as CanisterId; + +/// Update one or more of a canisters settings (i.e its controller, compute allocation, or memory allocation.) +#[derive(Clap)] +pub struct UpdateSettingsOpts { + /// Specifies the canister name to update. You must specify either canister name or the --all option. + canister_name: Option, + + /// Updates the settings of all canisters configured in the project dfx.json files. + #[clap(long, required_unless_present("canister-name"))] + all: bool, + + /// Specifies the identity name or the principal of the new controller. + #[clap(long)] + controller: Option, + + /// Specifies the canister's compute allocation. This should be a percent in the range [0..100] + #[clap(long, short('c'), validator(compute_allocation_validator))] + compute_allocation: Option, + + /// Specifies how much memory the canister is allowed to use in total. + /// This should be a value in the range [0..256 TB] + #[clap(long, validator(memory_allocation_validator))] + memory_allocation: Option, + + #[clap(long, validator(freezing_threshold_validator), setting = ArgSettings::Hidden)] + freezing_threshold: Option, +} + +pub async fn exec( + env: &dyn Environment, + opts: UpdateSettingsOpts, + call_sender: &CallSender, +) -> DfxResult { + let config = env.get_config_or_anyhow()?; + let timeout = expiry_duration(); + let config_interface = config.get_config(); + fetch_root_key_if_needed(env).await?; + + let controller = if let Some(controller) = opts.controller.clone() { + match CanisterId::from_text(controller.clone()) { + Ok(principal) => Some(principal), + Err(_) => { + let current_id = env.get_selected_identity().unwrap(); + if current_id == &controller { + Some(env.get_selected_identity_principal().unwrap()) + } else { + let identity_name = &controller; + let sender = IdentityManager::new(env)? + .instantiate_identity_from_name(&identity_name.clone())?; + Some(sender.sender().map_err(|err| anyhow!(err))?) + } + } + } + } else { + None + }; + + let canister_id_store = CanisterIdStore::for_env(env)?; + + if let Some(canister_name_or_id) = opts.canister_name.as_deref() { + let canister_id = match CanisterId::from_text(canister_name_or_id) { + Ok(id) => id, + Err(_) => canister_id_store.get(canister_name_or_id)?, + }; + let textual_cid = canister_id.to_text(); + let canister_name = canister_id_store + .get_name(&textual_cid) + .ok_or_else(|| anyhow!("Cannot find canister name for id '{}'.", textual_cid))?; + + let compute_allocation = get_compute_allocation( + opts.compute_allocation.clone(), + config_interface, + canister_name, + )?; + let memory_allocation = get_memory_allocation( + opts.memory_allocation.clone(), + config_interface, + canister_name, + )?; + let freezing_threshold = get_freezing_threshold( + opts.freezing_threshold.clone(), + config_interface, + canister_name, + )?; + update_settings( + env, + canister_id, + CanisterSettings { + controller: controller.clone(), + compute_allocation, + memory_allocation, + freezing_threshold, + }, + timeout, + call_sender, + ) + .await?; + if let Some(new_controller) = opts.controller.clone() { + println!( + "Updated {:?} as controller of {:?}.", + new_controller, canister_name_or_id + ); + }; + } else if opts.all { + // Update all canister settings. + if let Some(canisters) = &config.get_config().canisters { + for canister_name in canisters.keys() { + let canister_id = canister_id_store.get(canister_name)?; + let compute_allocation = get_compute_allocation( + opts.compute_allocation.clone(), + config_interface, + canister_name, + )?; + let memory_allocation = get_memory_allocation( + opts.memory_allocation.clone(), + config_interface, + canister_name, + )?; + let freezing_threshold = get_freezing_threshold( + opts.freezing_threshold.clone(), + config_interface, + canister_name, + )?; + update_settings( + env, + canister_id, + CanisterSettings { + controller: controller.clone(), + compute_allocation, + memory_allocation, + freezing_threshold, + }, + timeout, + call_sender, + ) + .await?; + if let Some(new_controller) = opts.controller.clone() { + println!( + "Updated {:?} as controller of {:?}.", + new_controller, canister_name + ); + }; + } + } + } else { + bail!("Cannot find canister name.") + } + + Ok(()) +} diff --git a/src/dfx/src/commands/wallet/upgrade.rs b/src/dfx/src/commands/wallet/upgrade.rs index 90a42bba63..bc4b7ade3c 100644 --- a/src/dfx/src/commands/wallet/upgrade.rs +++ b/src/dfx/src/commands/wallet/upgrade.rs @@ -10,7 +10,7 @@ use anyhow::{anyhow, bail}; use clap::Clap; use ic_agent::AgentError; use ic_utils::call::AsyncCall; -use ic_utils::interfaces::management_canister::InstallMode; +use ic_utils::interfaces::management_canister::builders::InstallMode; use ic_utils::interfaces::ManagementCanister; use std::io::Read; diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 5e015fcf88..ebb91d6b38 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -279,6 +279,10 @@ impl ConfigInterface { self.get_initialization_value(canister_name, "memory_allocation") } + pub fn get_freezing_threshold(&self, canister_name: &str) -> DfxResult> { + self.get_initialization_value(canister_name, "freezing_threshold") + } + fn get_initialization_value( &self, canister_name: &str, diff --git a/src/dfx/src/lib/ic_attributes/mod.rs b/src/dfx/src/lib/ic_attributes/mod.rs new file mode 100644 index 0000000000..03bd50a1ce --- /dev/null +++ b/src/dfx/src/lib/ic_attributes/mod.rs @@ -0,0 +1,55 @@ +use crate::config::dfinity::ConfigInterface; +use crate::lib::error::DfxResult; + +use humanize_rs::bytes::Bytes; +use ic_types::principal::Principal; +use ic_utils::interfaces::management_canister::attributes::{ + ComputeAllocation, FreezingThreshold, MemoryAllocation, +}; +use std::convert::TryFrom; + +pub struct CanisterSettings { + pub controller: Option, + pub compute_allocation: Option, + pub memory_allocation: Option, + pub freezing_threshold: Option, +} + +pub fn get_compute_allocation( + compute_allocation: Option, + config_interface: &ConfigInterface, + canister_name: &str, +) -> DfxResult> { + Ok(compute_allocation + .or(config_interface.get_compute_allocation(canister_name)?) + .map(|arg| { + ComputeAllocation::try_from(arg.parse::().unwrap()) + .expect("Compute Allocation must be a percentage.") + })) +} + +pub fn get_memory_allocation( + memory_allocation: Option, + config_interface: &ConfigInterface, + canister_name: &str, +) -> DfxResult> { + Ok(memory_allocation + .or(config_interface.get_memory_allocation(canister_name)?) + .map(|arg| { + MemoryAllocation::try_from(u64::try_from(arg.parse::().unwrap().size()).unwrap()) + .expect("Memory allocation must be between 0 and 2^48 (i.e 256TB), inclusively.") + })) +} + +pub fn get_freezing_threshold( + freezing_threshold: Option, + config_interface: &ConfigInterface, + canister_name: &str, +) -> DfxResult> { + Ok(freezing_threshold + .or(config_interface.get_freezing_threshold(canister_name)?) + .map(|arg| { + FreezingThreshold::try_from(arg.parse::().unwrap()) + .expect("Must be a value between 0 and 2^64-1 inclusive.") + })) +} diff --git a/src/dfx/src/lib/identity/mod.rs b/src/dfx/src/lib/identity/mod.rs index 6fc923f98b..d5637fa921 100644 --- a/src/dfx/src/lib/identity/mod.rs +++ b/src/dfx/src/lib/identity/mod.rs @@ -17,7 +17,7 @@ use ic_agent::Signature; use ic_identity_hsm::HardwareIdentity; use ic_types::Principal; use ic_utils::call::AsyncCall; -use ic_utils::interfaces::management_canister::InstallMode; +use ic_utils::interfaces::management_canister::builders::InstallMode; use ic_utils::interfaces::{ManagementCanister, Wallet}; use ic_utils::Canister; use serde::{Deserialize, Serialize}; @@ -350,7 +350,8 @@ impl Identity { .await? .0 } else { - mgr.provisional_create_canister_with_cycles(None) + mgr.create_canister() + .as_provisional_create_with_amount(None) .call_and_wait(waiter_with_timeout(expiry_duration())) .await? .0 diff --git a/src/dfx/src/lib/mod.rs b/src/dfx/src/lib/mod.rs index 8f8ea7c0f4..4b6e3b45c8 100644 --- a/src/dfx/src/lib/mod.rs +++ b/src/dfx/src/lib/mod.rs @@ -4,6 +4,7 @@ pub mod config; pub mod dist; pub mod environment; pub mod error; +pub mod ic_attributes; pub mod identity; pub mod installers; pub mod locations; diff --git a/src/dfx/src/lib/operations/canister/create_canister.rs b/src/dfx/src/lib/operations/canister/create_canister.rs index b900f219d1..4699b0276c 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::ic_attributes::CanisterSettings; use crate::lib::identity::identity_utils::CallSender; use crate::lib::identity::Identity; use crate::lib::models::canister_id_store::CanisterIdStore; @@ -25,6 +26,7 @@ pub async fn create_canister( timeout: Duration, with_cycles: Option<&str>, call_sender: &CallSender, + settings: CanisterSettings, ) -> DfxResult { let log = env.get_logger(); info!(log, "Creating canister {:?}...", canister_name); @@ -66,13 +68,22 @@ pub async fn create_canister( if network.is_ic { // Provisional commands are whitelisted on production mgr.create_canister() + .with_optional_controller(settings.controller) + .with_optional_compute_allocation(settings.compute_allocation) + .with_optional_memory_allocation(settings.memory_allocation) + .with_optional_freezing_threshold(settings.freezing_threshold) .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) + mgr.create_canister() + .as_provisional_create_with_amount(cycles) + .with_optional_controller(settings.controller) + .with_optional_compute_allocation(settings.compute_allocation) + .with_optional_memory_allocation(settings.memory_allocation) + .with_optional_freezing_threshold(settings.freezing_threshold) .call_and_wait(waiter_with_timeout(timeout)) .await? .0 @@ -95,7 +106,13 @@ pub async fn create_canister( |amount| amount.parse::().unwrap(), ); wallet - .wallet_create_canister(cycles, None) + .wallet_create_canister( + cycles, + settings.controller, + settings.compute_allocation, + settings.memory_allocation, + settings.freezing_threshold, + ) .call_and_wait(waiter_with_timeout(timeout)) .await? .0 @@ -104,7 +121,6 @@ pub async fn create_canister( } } }; - let canister_id = cid.to_text(); info!( log, diff --git a/src/dfx/src/lib/operations/canister/deploy_canisters.rs b/src/dfx/src/lib/operations/canister/deploy_canisters.rs index b86199dab6..fb440f9c06 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::ic_attributes::CanisterSettings; use crate::lib::identity::identity_utils::CallSender; use crate::lib::models::canister::CanisterPool; use crate::lib::models::canister_id_store::CanisterIdStore; @@ -12,7 +13,10 @@ use crate::util::{blob_from_arguments, get_candid_init_type}; use anyhow::{anyhow, bail}; use humanize_rs::bytes::Bytes; use ic_agent::AgentError; -use ic_utils::interfaces::management_canister::{ComputeAllocation, InstallMode, MemoryAllocation}; +use ic_utils::interfaces::management_canister::attributes::{ + ComputeAllocation, FreezingThreshold, MemoryAllocation, +}; +use ic_utils::interfaces::management_canister::builders::InstallMode; use slog::info; use std::convert::TryFrom; use std::time::Duration; @@ -47,6 +51,7 @@ pub async fn deploy_canisters( timeout, with_cycles, call_sender, + &config, ) .await?; @@ -84,6 +89,7 @@ async fn register_canisters( timeout: Duration, with_cycles: Option<&str>, call_sender: &CallSender, + config: &Config, ) -> DfxResult { let canisters_to_create = canister_names .iter() @@ -95,7 +101,49 @@ 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, &call_sender).await?; + let config_interface = config.get_config(); + let compute_allocation = + config_interface + .get_compute_allocation(canister_name)? + .map(|arg| { + ComputeAllocation::try_from(arg.parse::().unwrap()) + .expect("Compute Allocation must be a percentage.") + }); + let memory_allocation = + config_interface + .get_memory_allocation(canister_name)? + .map(|arg| { + MemoryAllocation::try_from( + u64::try_from(arg.parse::().unwrap().size()).unwrap(), + ) + .expect( + "Memory allocation must be between 0 and 2^48 (i.e 256TB), inclusively.", + ) + }); + let freezing_threshold = + config_interface + .get_freezing_threshold(canister_name)? + .map(|arg| { + FreezingThreshold::try_from( + u128::try_from(arg.parse::().unwrap().size()).unwrap(), + ) + .expect("Freezing threshold must be between 0 and 2^64-1, inclusively.") + }); + let controller = None; + create_canister( + env, + &canister_name, + timeout, + with_cycles, + &call_sender, + CanisterSettings { + controller, + compute_allocation, + memory_allocation, + freezing_threshold, + }, + ) + .await?; } } Ok(()) @@ -154,31 +202,12 @@ async fn install_canisters( let init_type = maybe_path.and_then(|path| get_candid_init_type(&path)); let install_args = blob_from_arguments(argument, None, argument_type, &init_type)?; - let config_interface = config.get_config(); - let compute_allocation = - config_interface - .get_compute_allocation(canister_name)? - .map(|arg| { - ComputeAllocation::try_from(arg.parse::().unwrap()) - .expect("Compute Allocation must be a percentage.") - }); - let memory_allocation = config_interface - .get_memory_allocation(canister_name)? - .map(|arg| { - MemoryAllocation::try_from( - u64::try_from(arg.parse::().unwrap().size()).unwrap(), - ) - .expect("Memory allocation must be between 0 and 2^48 (i.e 256TB), inclusively.") - }); - install_canister( env, &agent, &canister_info, &install_args, - compute_allocation, install_mode, - memory_allocation, timeout, &call_sender, ) diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index 9b2b469efd..b92ef6867b 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -9,9 +9,7 @@ use crate::lib::waiter::waiter_with_timeout; use anyhow::Context; use ic_agent::Agent; use ic_utils::call::AsyncCall; -use ic_utils::interfaces::management_canister::{ - CanisterInstall, ComputeAllocation, InstallMode, MemoryAllocation, -}; +use ic_utils::interfaces::management_canister::builders::{CanisterInstall, InstallMode}; use ic_utils::interfaces::ManagementCanister; use ic_utils::Canister; use slog::info; @@ -23,9 +21,7 @@ pub async fn install_canister( agent: &Agent, canister_info: &CanisterInfo, args: &[u8], - compute_allocation: Option, mode: InstallMode, - memory_allocation: Option, timeout: Duration, call_sender: &CallSender, ) -> DfxResult { @@ -61,16 +57,6 @@ pub async fn install_canister( .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)) @@ -84,8 +70,6 @@ pub async fn install_canister( 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( diff --git a/src/dfx/src/lib/operations/canister/mod.rs b/src/dfx/src/lib/operations/canister/mod.rs index 20e95ac988..ef4b274bab 100644 --- a/src/dfx/src/lib/operations/canister/mod.rs +++ b/src/dfx/src/lib/operations/canister/mod.rs @@ -9,6 +9,7 @@ pub use install_canister::install_canister; use crate::lib::canister_info::CanisterInfo; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; +use crate::lib::ic_attributes::CanisterSettings as DfxCanisterSettings; use crate::lib::identity::identity_utils::CallSender; use crate::lib::identity::Identity; use crate::lib::waiter::waiter_with_timeout; @@ -18,6 +19,7 @@ use candid::CandidType; use ic_types::principal::Principal as CanisterId; use ic_types::Principal; use ic_utils::call::AsyncCall; +use ic_utils::interfaces::management_canister::builders::CanisterSettings; use ic_utils::interfaces::management_canister::StatusCallResult; use ic_utils::interfaces::ManagementCanister; use std::path::PathBuf; @@ -131,26 +133,39 @@ pub async fn stop_canister( Ok(()) } -pub async fn set_controller( +pub async fn update_settings( env: &dyn Environment, canister_id: Principal, - new_controller: Principal, + settings: DfxCanisterSettings, timeout: Duration, call_sender: &CallSender, ) -> DfxResult { - #[derive(CandidType)] + #[derive(candid::CandidType)] struct In { canister_id: Principal, - new_controller: Principal, + settings: CanisterSettings, } - let _: () = do_management_call( env, canister_id.clone(), - "set_controller", + "update_settings", In { canister_id, - new_controller, + settings: CanisterSettings { + controller: settings.controller, + compute_allocation: settings + .compute_allocation + .map(u8::from) + .map(candid::Nat::from), + memory_allocation: settings + .memory_allocation + .map(u64::from) + .map(candid::Nat::from), + freezing_threshold: settings + .freezing_threshold + .map(u64::from) + .map(candid::Nat::from), + }, }, timeout, call_sender, diff --git a/src/dfx/src/util/clap/validators.rs b/src/dfx/src/util/clap/validators.rs index 1961be7997..9bea270787 100644 --- a/src/dfx/src/util/clap/validators.rs +++ b/src/dfx/src/util/clap/validators.rs @@ -43,6 +43,15 @@ pub fn memory_allocation_validator(memory_allocation: &str) -> Result<(), String Err("Must be a value between 0..256 TB inclusive.".to_string()) } +pub fn freezing_threshold_validator(freezing_threshold: &str) -> Result<(), String> { + if let Ok(num) = freezing_threshold.parse::() { + if num <= (2_u128.pow(64) - 1) { + return Ok(()); + } + } + Err("Must be a value between 0 and 2^64-1 inclusive".to_string()) +} + /// Validate a String can be a valid project name. /// A project name is valid if it starts with a letter, and is alphanumeric (with hyphens). /// It cannot end with a dash. diff --git a/src/distributed/wallet.did b/src/distributed/wallet.did index 97ca5171ae..3d604aa0fe 100644 --- a/src/distributed/wallet.did +++ b/src/distributed/wallet.did @@ -71,6 +71,18 @@ type ResultCall = variant { Err : text; }; +type CanisterSettings = record { + controller: opt principal; + compute_allocation: opt nat; + memory_allocation: opt nat; + freezing_threshold: opt nat; +}; + +type CreateCanisterArgs = record { + cycles: nat64; + settings: CanisterSettings; +}; + service : { // Wallet Name name: () -> (opt text) query; @@ -92,15 +104,9 @@ service : { wallet_receive: () -> (); // Endpoint for receiving cycles. // Managing canister - wallet_create_canister: (record { - cycles: nat64; - controller: opt principal; // If omitted, set the controller to this wallet. - }) -> (ResultCreate); + wallet_create_canister: (CreateCanisterArgs) -> (ResultCreate); - wallet_create_wallet: (record { - cycles: nat64; - controller: opt principal; // If omitted, set the controller to this wallet. - }) -> (ResultCreate); + wallet_create_wallet: (CreateCanisterArgs) -> (ResultCreate); wallet_store_wallet_wasm: (record { wasm_module: blob; diff --git a/src/distributed/wallet.wasm b/src/distributed/wallet.wasm index 22a73f7e4a..e88ef114cc 100644 Binary files a/src/distributed/wallet.wasm and b/src/distributed/wallet.wasm differ