diff --git a/CHANGELOG.md b/CHANGELOG.md index e74280b3c..b3f4772c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +* chore!: use `--tcycles` for consistency instead of raw `--cycles` amounts * fix: Validate explicit canister paths and throw an error if `canister.yaml` is not found # v0.1.0-beta.3 diff --git a/crates/icp-canister-interfaces/src/cycles_ledger.rs b/crates/icp-canister-interfaces/src/cycles_ledger.rs index 9a090e348..015a04e78 100644 --- a/crates/icp-canister-interfaces/src/cycles_ledger.rs +++ b/crates/icp-canister-interfaces/src/cycles_ledger.rs @@ -117,8 +117,10 @@ impl CreateCanisterError { CreateCanisterError::TooOld => "created_at_time is too old.".to_string(), CreateCanisterError::InsufficientFunds { balance } => { format!( - "Insufficient cycles. Requested: {requested_cycles} cycles, available balance: {balance} cycles. - use `icp cycles mint` to get more cycles or use `--cycles` to specify a different amount." + "Insufficient cycles. Requested: {} TCYCLES, available balance: {} TCYCLES. + use `icp cycles mint` to get more cycles or use `--tcycles` to specify a different amount.", + BigDecimal::new(requested_cycles.into(), CYCLES_LEDGER_DECIMALS), + BigDecimal::from_biguint(balance.0.clone(), CYCLES_LEDGER_DECIMALS) ) } } diff --git a/crates/icp-cli/src/commands/canister/create.rs b/crates/icp-cli/src/commands/canister/create.rs index e3d08e6d7..c53054fe2 100644 --- a/crates/icp-cli/src/commands/canister/create.rs +++ b/crates/icp-cli/src/commands/canister/create.rs @@ -1,8 +1,9 @@ use anyhow::anyhow; +use bigdecimal::BigDecimal; use candid::{Nat, Principal}; use clap::Args; use icp::context::Context; -use icp::{Canister, context::CanisterSelection, prelude::*}; +use icp::{Canister, context::CanisterSelection}; use icp_canister_interfaces::cycles_ledger::CanisterSettingsArg; use crate::{ @@ -11,7 +12,7 @@ use crate::{ progress::{ProgressManager, ProgressManagerSettings}, }; -pub(crate) const DEFAULT_CANISTER_CYCLES: u128 = 2 * TRILLION; +pub(crate) const DEFAULT_CANISTER_TCYCLES: &str = "2"; #[derive(Clone, Debug, Default, Args)] pub(crate) struct CanisterSettings { @@ -49,9 +50,9 @@ pub(crate) struct CreateArgs { #[arg(long, short = 'q')] pub(crate) quiet: bool, - /// Cycles to fund canister creation (in raw cycles). - #[arg(long, default_value_t = DEFAULT_CANISTER_CYCLES)] - pub(crate) cycles: u128, + /// Cycles to fund canister creation (in TCYCLES). + #[arg(long, default_value = DEFAULT_CANISTER_TCYCLES)] + pub(crate) tcycles: BigDecimal, /// The subnet to create canisters on. #[arg(long)] @@ -128,7 +129,7 @@ pub(crate) async fn exec(ctx: &Context, args: &CreateArgs) -> Result<(), anyhow: .collect(); let progress_manager = ProgressManager::new(ProgressManagerSettings { hidden: ctx.debug }); let create_operation = - CreateOperation::new(agent, args.subnet, args.cycles, existing_canisters); + CreateOperation::new(agent, args.subnet, args.tcycles.clone(), existing_canisters); let canister_settings = args.canister_settings_with_default(&canister_info); let pb = progress_manager.create_progress_bar(&canister); diff --git a/crates/icp-cli/src/commands/cycles/mint.rs b/crates/icp-cli/src/commands/cycles/mint.rs index 727656b84..f7059883f 100644 --- a/crates/icp-cli/src/commands/cycles/mint.rs +++ b/crates/icp-cli/src/commands/cycles/mint.rs @@ -9,12 +9,12 @@ use crate::operations::token::mint::mint_cycles; #[derive(Debug, Args)] pub(crate) struct MintArgs { /// Amount of ICP to mint to cycles. - #[arg(long, conflicts_with = "cycles")] + #[arg(long, conflicts_with = "tcycles")] pub(crate) icp: Option, - /// Amount of cycles to mint. Automatically determines the amount of ICP needed. + /// Amount of cycles to mint (in TCYCLES). Automatically determines the amount of ICP needed. #[arg(long, conflicts_with = "icp")] - pub(crate) cycles: Option, + pub(crate) tcycles: Option, #[command(flatten)] pub(crate) token_command_args: TokenCommandArgs, @@ -22,8 +22,8 @@ pub(crate) struct MintArgs { pub(crate) async fn exec(ctx: &Context, args: &MintArgs) -> Result<(), anyhow::Error> { // Validate args - if args.icp.is_none() && args.cycles.is_none() { - bail!("no amount specified. Use --icp or --cycles"); + if args.icp.is_none() && args.tcycles.is_none() { + bail!("no amount specified. Use --icp or --tcycles"); } let selections = args.token_command_args.selections(); @@ -38,7 +38,7 @@ pub(crate) async fn exec(ctx: &Context, args: &MintArgs) -> Result<(), anyhow::E .await?; // Execute mint operation - let mint_info = mint_cycles(&agent, args.icp.as_ref(), args.cycles).await?; + let mint_info = mint_cycles(&agent, args.icp.as_ref(), args.tcycles.as_ref()).await?; // Display results let _ = ctx.term.write_line(&format!( diff --git a/crates/icp-cli/src/commands/deploy/mod.rs b/crates/icp-cli/src/commands/deploy/mod.rs index a91e606b8..f33c7ea87 100644 --- a/crates/icp-cli/src/commands/deploy/mod.rs +++ b/crates/icp-cli/src/commands/deploy/mod.rs @@ -1,4 +1,5 @@ use anyhow::anyhow; +use bigdecimal::BigDecimal; use candid::{CandidType, Principal}; use clap::Args; use futures::{StreamExt, future::try_join_all, stream::FuturesOrdered}; @@ -40,9 +41,9 @@ pub(crate) struct DeployArgs { #[arg(long)] pub(crate) controller: Vec, - /// Cycles to fund canister creation (in cycles). - #[arg(long, default_value_t = create::DEFAULT_CANISTER_CYCLES)] - pub(crate) cycles: u128, + /// Cycles to fund canister creation (in TCYCLES). + #[arg(long, default_value = create::DEFAULT_CANISTER_TCYCLES)] + pub(crate) tcycles: BigDecimal, #[command(flatten)] pub(crate) identity: IdentityOpt, @@ -115,7 +116,7 @@ pub(crate) async fn exec(ctx: &Context, args: &DeployArgs) -> Result<(), anyhow: let create_operation = CreateOperation::new( agent.clone(), args.subnet, - args.cycles, + args.tcycles.clone(), existing_canisters.into_values().collect(), ); let mut futs = FuturesOrdered::new(); diff --git a/crates/icp-cli/src/operations/create.rs b/crates/icp-cli/src/operations/create.rs index e85ab3e95..2a6ab29a1 100644 --- a/crates/icp-cli/src/operations/create.rs +++ b/crates/icp-cli/src/operations/create.rs @@ -1,9 +1,10 @@ +use bigdecimal::{BigDecimal, ToPrimitive}; use candid::{Decode, Encode, Nat, Principal}; use ic_agent::{Agent, AgentError}; use icp_canister_interfaces::{ cycles_ledger::{ - CYCLES_LEDGER_PRINCIPAL, CanisterSettingsArg, CreateCanisterArgs, CreateCanisterResponse, - CreationArgs, SubnetSelectionArg, + CYCLES_LEDGER_DECIMALS, CYCLES_LEDGER_PRINCIPAL, CanisterSettingsArg, CreateCanisterArgs, + CreateCanisterResponse, CreationArgs, SubnetSelectionArg, }, cycles_minting_canister::CYCLES_MINTING_CANISTER_PRINCIPAL, registry::{GetSubnetForCanisterRequest, GetSubnetForCanisterResult, REGISTRY_PRINCIPAL}, @@ -44,12 +45,15 @@ pub enum CreateOperationError { #[snafu(display("failed to resolve subnet: {message}"))] SubnetResolution { message: String }, + + #[snafu(display("cycles amount overflow"))] + CyclesAmountOverflow, } struct CreateOperationInner { agent: Agent, subnet: Option, - cycles: u128, + tcycles: BigDecimal, existing_canisters: Vec, resolved_subnet: OnceCell>, } @@ -70,14 +74,14 @@ impl CreateOperation { pub fn new( agent: Agent, subnet: Option, - cycles: u128, + tcycles: BigDecimal, existing_canisters: Vec, ) -> Self { Self { inner: Arc::new(CreateOperationInner { agent, subnet, - cycles, + tcycles, existing_canisters, resolved_subnet: OnceCell::new(), }), @@ -92,6 +96,10 @@ impl CreateOperation { &self, settings: &CanisterSettingsArg, ) -> Result { + let raw_cycles = (&self.inner.tcycles * 10u128.pow(CYCLES_LEDGER_DECIMALS as u32)) + .to_u128() + .ok_or(CreateOperationError::CyclesAmountOverflow)?; + let creation_args = CreationArgs { subnet_selection: Some(SubnetSelectionArg::Subnet { subnet: self @@ -104,7 +112,7 @@ impl CreateOperation { let arg = CreateCanisterArgs { from_subaccount: None, created_at_time: None, - amount: Nat::from(self.inner.cycles), + amount: Nat::from(raw_cycles), creation_args: Some(creation_args), }; @@ -123,7 +131,7 @@ impl CreateOperation { CreateCanisterResponse::Ok { canister_id, .. } => canister_id, CreateCanisterResponse::Err(err) => { return CreateCanisterSnafu { - message: err.format_error(self.inner.cycles), + message: err.format_error(raw_cycles), } .fail(); } diff --git a/crates/icp-cli/src/operations/token/mint.rs b/crates/icp-cli/src/operations/token/mint.rs index 2e7b3cbc8..9055f3bcd 100644 --- a/crates/icp-cli/src/operations/token/mint.rs +++ b/crates/icp-cli/src/operations/token/mint.rs @@ -64,9 +64,9 @@ pub struct MintInfo { /// /// * `agent` - The IC agent to use for queries and updates /// * `icp_amount` - Optional ICP amount to convert to cycles -/// * `cycles_amount` - Optional desired cycles amount (will calculate required ICP) +/// * `tcycles_amount` - Optional desired cycles amount in TCYCLES (will calculate required ICP) /// -/// One of `icp_amount` or `cycles_amount` must be provided (but not both). +/// One of `icp_amount` or `tcycles_amount` must be provided (but not both). /// /// # Returns /// @@ -74,7 +74,7 @@ pub struct MintInfo { pub async fn mint_cycles( agent: &Agent, icp_amount: Option<&BigDecimal>, - cycles_amount: Option, + tcycles_amount: Option<&BigDecimal>, ) -> Result { // Get user principal let user_principal = agent @@ -86,7 +86,11 @@ pub async fn mint_cycles( (icp_amount * 100_000_000_u64) .to_u64() .ok_or(MintCyclesError::IcpAmountOverflow)? - } else if let Some(cycles_amount) = cycles_amount { + } else if let Some(tcycles_amount) = tcycles_amount { + let cycles_amount = (tcycles_amount.clone() * 10u128.pow(CYCLES_LEDGER_DECIMALS as u32)) + .to_u128() + .ok_or(MintCyclesError::IcpAmountOverflow)?; + // Query CMC for conversion rate let cmc_response = agent .query( diff --git a/crates/icp-cli/tests/canister_create_tests.rs b/crates/icp-cli/tests/canister_create_tests.rs index ef323ef0a..890b06bd1 100644 --- a/crates/icp-cli/tests/canister_create_tests.rs +++ b/crates/icp-cli/tests/canister_create_tests.rs @@ -122,8 +122,8 @@ async fn canister_create_with_settings() { "my-canister", "--environment", "random-environment", - "--cycles", - &format!("{}", 70 * TRILLION), /* 70 TCYCLES because compute allocation is expensive */ + "--tcycles", + "70", /* 70 TCYCLES because compute allocation is expensive */ ]) .assert() .success(); @@ -202,8 +202,8 @@ async fn canister_create_with_settings_cmdline_override() { "2", "--environment", "random-environment", - "--cycles", - &format!("{}", 70 * TRILLION), /* 70 TCYCLES because compute allocation is expensive */ + "--tcycles", + "70", /* 70 TCYCLES because compute allocation is expensive */ ]) .assert() .success(); diff --git a/crates/icp-cli/tests/canister_settings_tests.rs b/crates/icp-cli/tests/canister_settings_tests.rs index 9260c8825..8b6e2a969 100644 --- a/crates/icp-cli/tests/canister_settings_tests.rs +++ b/crates/icp-cli/tests/canister_settings_tests.rs @@ -682,8 +682,8 @@ async fn canister_settings_update_miscellaneous() { "deploy", "--subnet", common::SUBNET_ID, - "--cycles", - &format!("{}", 120 * TRILLION), // 120 TCYCLES because compute allocation is expensive + "--tcycles", + "120", // 120 TCYCLES because compute allocation is expensive "--environment", "random-environment", ]) diff --git a/crates/icp-cli/tests/common/clients/icp_cli.rs b/crates/icp-cli/tests/common/clients/icp_cli.rs index 35ab0dbdd..b882e6748 100644 --- a/crates/icp-cli/tests/common/clients/icp_cli.rs +++ b/crates/icp-cli/tests/common/clients/icp_cli.rs @@ -1,5 +1,7 @@ +use bigdecimal::BigDecimal; use candid::Principal; use icp::{prelude::*, project::DEFAULT_LOCAL_ENVIRONMENT_NAME}; +use icp_canister_interfaces::cycles_ledger::CYCLES_LEDGER_DECIMALS; use crate::common::TestContext; @@ -78,14 +80,15 @@ impl<'a> Client<'a> { } pub(crate) fn mint_cycles(&self, amount: u128) { + let tcycles = BigDecimal::new(amount.into(), CYCLES_LEDGER_DECIMALS); self.ctx .icp() .current_dir(&self.current_dir) .args([ "cycles", "mint", - "--cycles", - &amount.to_string(), + "--tcycles", + &tcycles.to_string(), "--environment", &self.environment, ]) diff --git a/crates/icp-cli/tests/cycles_tests.rs b/crates/icp-cli/tests/cycles_tests.rs index 22acceb16..87bf6943c 100644 --- a/crates/icp-cli/tests/cycles_tests.rs +++ b/crates/icp-cli/tests/cycles_tests.rs @@ -74,8 +74,8 @@ async fn cycles_balance() { .args([ "cycles", "mint", - "--cycles", - "1000000000", + "--tcycles", + "0.001", "--environment", "random-environment", ]) @@ -89,8 +89,8 @@ async fn cycles_balance() { .args([ "cycles", "mint", - "--cycles", - "1500000000", + "--tcycles", + "0.0015", "--environment", "random-environment", ]) diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 4e5eac6c5..bdda0f141 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -160,9 +160,9 @@ Create a canister on a network * `--freezing-threshold ` — Optional freezing threshold in seconds. Controls how long a canister can be inactive before being frozen * `--reserved-cycles-limit ` — Optional reserved cycles limit. If set, the canister cannot consume more than this many cycles * `-q`, `--quiet` — Suppress human-readable output; print only canister IDs, one per line, to stdout -* `--cycles ` — Cycles to fund canister creation (in raw cycles) +* `--tcycles ` — Cycles to fund canister creation (in TCYCLES) - Default value: `2000000000000` + Default value: `2` * `--subnet ` — The subnet to create canisters on @@ -445,7 +445,7 @@ Convert icp to cycles ###### **Options:** * `--icp ` — Amount of ICP to mint to cycles -* `--cycles ` — Amount of cycles to mint. Automatically determines the amount of ICP needed +* `--tcycles ` — Amount of cycles to mint (in TCYCLES). Automatically determines the amount of ICP needed * `--network ` — Name of the network to target, conflicts with environment argument * `--mainnet` — Shorthand for --network=mainnet * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used @@ -474,9 +474,9 @@ Deploy a project to an environment * `--subnet ` — The subnet to use for the canisters being deployed * `--controller ` — One or more controllers for the canisters being deployed. Repeat `--controller` to specify multiple -* `--cycles ` — Cycles to fund canister creation (in cycles) +* `--tcycles ` — Cycles to fund canister creation (in TCYCLES) - Default value: `2000000000000` + Default value: `2` * `--identity ` — The user identity to run this command as * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--ic` — Shorthand for --environment=ic