diff --git a/crates/pop-cli/src/commands/new/parachain.rs b/crates/pop-cli/src/commands/new/parachain.rs index 735300de..96e0d1f3 100644 --- a/crates/pop-cli/src/commands/new/parachain.rs +++ b/crates/pop-cli/src/commands/new/parachain.rs @@ -12,9 +12,14 @@ use std::{ }; use cliclack::{clear_screen, confirm, input, intro, log, outro, outro_cancel, set_theme}; -use pop_parachains::{instantiate_template_dir, Config, Git, GitHub, Provider, Release, Template}; +use pop_parachains::{ + instantiate_template_dir, is_initial_endowment_valid, Config, Git, GitHub, Provider, Release, + Template, +}; use strum::VariantArray; +const DEFAULT_INITIAL_ENDOWMENT: &str = "1u64 << 60"; + #[derive(Args)] pub struct NewParachainCommand { #[arg(help = "Name of the project. If empty assistance in the process will be provided.")] @@ -40,7 +45,7 @@ pub struct NewParachainCommand { long = "endowment", short, help = "Token Endowment for dev accounts", - default_value = "1u64 << 60" + default_value = DEFAULT_INITIAL_ENDOWMENT )] pub(crate) initial_endowment: Option, #[arg( @@ -82,11 +87,29 @@ impl NewParachainCommand { }; is_template_supported(provider, &template)?; + let mut initial_endowment = self.initial_endowment.clone(); + if initial_endowment.is_some() + && !is_initial_endowment_valid(&initial_endowment.clone().unwrap()) + { + log::warning("⚠️ The specified initial endowment is not valid")?; + //Prompt the user if want to use the one by default + if !confirm(format!( + "📦 Would you like to use the default {}?", + DEFAULT_INITIAL_ENDOWMENT + )) + .initial_value(true) + .interact()? + { + outro_cancel("🚫 Cannot create a parachain with an incorrect initial endowment value.")?; + return Ok(()); + } + initial_endowment = Some(DEFAULT_INITIAL_ENDOWMENT.to_string()); + } let config = get_customization_value( &template, self.symbol.clone(), self.decimals, - self.initial_endowment.clone(), + initial_endowment.clone(), )?; generate_parachain_from_template(name, provider, &template, None, config) @@ -280,11 +303,25 @@ fn prompt_customizable_options() -> Result { .interact()?; let decimals: u8 = decimals_input.parse::().expect("input has to be a number"); - let endowment: String = input("And the initial endowment for dev accounts?") + let mut initial_endowment: String = input("And the initial endowment for dev accounts?") .placeholder("1u64 << 60") .default_input("1u64 << 60") .interact()?; - Ok(Config { symbol, decimals, initial_endowment: endowment }) + if !is_initial_endowment_valid(&initial_endowment) { + outro_cancel("⚠️ The specified initial endowment is not valid")?; + //Prompt the user if want to use the one by default + if !confirm(format!("📦 Would you like to use the default {}?", DEFAULT_INITIAL_ENDOWMENT)) + .initial_value(true) + .interact()? + { + outro_cancel( + "🚫 Cannot create a parachain with an incorrect initial endowment value.", + )?; + return Err(anyhow::anyhow!("incorrect initial endowment value")); + } + initial_endowment = DEFAULT_INITIAL_ENDOWMENT.to_string(); + } + Ok(Config { symbol, decimals, initial_endowment }) } #[cfg(test)] diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 6e636099..8d8652f6 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -51,4 +51,7 @@ pub enum Error { #[error("Template error: {0}")] TemplateError(#[from] templates::Error), + + #[error("Failed to parse the endowment value")] + EndowmentError, } diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index a5fa0af9..d697894d 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -14,6 +14,7 @@ pub use new_parachain::instantiate_template_dir; pub use templates::{Config, Provider, Template}; pub use up::{Status, Zombienet}; pub use utils::git::{Git, GitHub, Release}; +pub use utils::helpers::is_initial_endowment_valid; pub use utils::pallet_helpers::resolve_pallet_path; // External exports pub use zombienet_sdk::NetworkNode; diff --git a/crates/pop-parachains/src/utils/helpers.rs b/crates/pop-parachains/src/utils/helpers.rs index 37c07d4b..8f41ce95 100644 --- a/crates/pop-parachains/src/utils/helpers.rs +++ b/crates/pop-parachains/src/utils/helpers.rs @@ -24,6 +24,32 @@ pub(crate) fn sanitize(target: &Path) -> Result<(), Error> { Ok(()) } +pub fn is_initial_endowment_valid(initial_endowment: &str) -> bool { + initial_endowment.parse::().is_ok() + || is_valid_bitwise_left_shift(initial_endowment).is_ok() +} +// Auxiliar method to check if the endowment input with a shift left (1u64 << 60) format is valid. +// Parse the self << rhs format and check the shift left operation is valid. +fn is_valid_bitwise_left_shift(initial_endowment: &str) -> Result { + let v: Vec<&str> = initial_endowment.split(" << ").collect(); + if v.len() < 2 { + return Err(Error::EndowmentError); + } + let left = v[0] + .split("u") // parse 1u64 characters + .take(1) + .collect::() + .parse::() + .or_else(|_e| Err(Error::EndowmentError))?; + let right = v[1] + .chars() + .filter(|c| c.is_numeric()) // parse 1u64 characters + .collect::() + .parse::() + .or_else(|_e| Err(Error::EndowmentError))?; + left.checked_shl(right).ok_or(Error::EndowmentError) +} + pub(crate) fn write_to_file(path: &Path, contents: &str) -> Result<(), Error> { let mut file = OpenOptions::new() .write(true) @@ -50,3 +76,23 @@ pub(crate) fn write_to_file(path: &Path, contents: &str) -> Result<(), Error> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_initial_endowment_valid() { + assert_eq!(is_initial_endowment_valid("100000"), true); + assert_eq!(is_initial_endowment_valid("1u64 << 60"), true); + assert_eq!(is_initial_endowment_valid("wrong"), false); + assert_eq!(is_initial_endowment_valid(" "), false); + } + #[test] + fn test_left_shift() { + // Values from https://stackoverflow.com/questions/56392875/how-can-i-initialize-a-users-balance-in-a-substrate-blockchain + assert_eq!(is_valid_bitwise_left_shift("1 << 60").unwrap(), 1152921504606846976); + let result = is_valid_bitwise_left_shift("wrong"); + assert!(result.is_err()); + } +}