From 77309e2fa26198011a17d20c3732bf1061237319 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 10 Feb 2025 11:23:52 +0100 Subject: [PATCH 01/27] feat: deploy a parachain commands --- crates/pop-cli/src/commands/up/mod.rs | 15 +++++++++++--- crates/pop-cli/src/commands/up/parachain.rs | 23 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 crates/pop-cli/src/commands/up/parachain.rs diff --git a/crates/pop-cli/src/commands/up/mod.rs b/crates/pop-cli/src/commands/up/mod.rs index 5d409065..362de9a9 100644 --- a/crates/pop-cli/src/commands/up/mod.rs +++ b/crates/pop-cli/src/commands/up/mod.rs @@ -11,6 +11,8 @@ use std::path::PathBuf; mod contract; #[cfg(feature = "parachain")] mod network; +#[cfg(feature = "parachain")] +mod parachain; /// Arguments for launching or deploying a project. #[derive(Args, Clone)] @@ -29,6 +31,10 @@ pub(crate) struct UpArgs { #[cfg(feature = "contract")] pub(crate) contract: contract::UpContractCommand, + #[command(flatten)] + #[cfg(feature = "parachain")] + pub(crate) parachain: parachain::UpParachainCommand, + #[command(subcommand)] pub(crate) command: Option, } @@ -73,7 +79,9 @@ impl Command { } #[cfg(feature = "parachain")] if pop_parachains::is_supported(project_path.as_deref())? { - cli.warning("Parachain deployment is currently not implemented.")?; + let mut cmd = args.parachain; + cmd.path = project_path; + cmd.execute(cli).await?; return Ok("parachain"); } cli.warning( @@ -85,7 +93,7 @@ impl Command { #[cfg(test)] mod tests { - use super::{contract::UpContractCommand, *}; + use super::{contract::UpContractCommand, parachain::UpParachainCommand, *}; use cli::MockCli; use duct::cmd; @@ -114,6 +122,7 @@ mod tests { skip_confirm: false, valid: false, }, + parachain: UpParachainCommand::default(), command: None, }) } @@ -148,7 +157,7 @@ mod tests { let args = create_up_args(project_path)?; let mut cli = - MockCli::new().expect_warning("Parachain deployment is currently not implemented."); + MockCli::new().expect_warning("Deploy a parachain"); assert_eq!(Command::execute_project_deployment(args, &mut cli).await?, "parachain"); cli.verify() } diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs new file mode 100644 index 00000000..beeca670 --- /dev/null +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::cli; +use clap::Args; +use std::path::PathBuf; + +const HELP_HEADER: &str = "Parachain deployment options"; + +#[derive(Args, Clone, Default)] +#[clap(next_help_heading = HELP_HEADER)] +pub struct UpParachainCommand { + /// Path to the contract build directory. + #[clap(skip)] + pub(crate) path: Option, +} + +impl UpParachainCommand { + /// Executes the command. + pub(crate) async fn execute(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<()> { + cli.intro("Deploy a parachain")?; + Ok(()) + } +} From f9dffc6c9ab6319534947c4e192cc032c0ea09d6 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 10 Feb 2025 17:31:20 +0100 Subject: [PATCH 02/27] feat: build specs in pop up --- crates/pop-cli/src/commands/build/spec.rs | 89 ++++++++++++++------- crates/pop-cli/src/commands/up/mod.rs | 8 +- crates/pop-cli/src/commands/up/parachain.rs | 35 +++++++- 3 files changed, 100 insertions(+), 32 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 7e9ba052..c09220bf 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -9,7 +9,7 @@ use crate::{ style::style, }; use clap::{Args, ValueEnum}; -use cliclack::spinner; +use cliclack::{spinner, ProgressBar}; use pop_common::Profile; use pop_parachains::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, @@ -193,7 +193,7 @@ impl BuildSpecCommand { /// /// # Arguments /// * `cli` - The cli. - async fn configure_build_spec( + pub async fn configure_build_spec( self, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { @@ -439,7 +439,7 @@ impl BuildSpecCommand { // Represents the configuration for building a chain specification. #[derive(Debug)] -struct BuildSpec { +pub struct BuildSpec { output_file: PathBuf, profile: Profile, id: u32, @@ -461,39 +461,18 @@ impl BuildSpec { fn build(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { cli.intro("Building your chain spec")?; let mut generated_files = vec![]; - let BuildSpec { - ref output_file, - ref profile, - id, - default_bootnode, - ref chain, - genesis_state, - genesis_code, - .. - } = self; + let BuildSpec { ref output_file, ref profile, id, genesis_code, genesis_state, .. } = self; // Ensure binary is built. let binary_path = ensure_binary_exists(cli, profile)?; let spinner = spinner(); - spinner.start("Generating chain specification..."); - // Generate chain spec. - generate_plain_chain_spec(&binary_path, output_file, default_bootnode, chain)?; - // Customize spec based on input. - self.customize()?; + let raw_chain_spec = self.generate_chain_spec(&binary_path, &spinner)?; + generated_files.push(format!( "Plain text chain specification file generated at: {}", &output_file.display() )); - // Generate raw spec. - spinner.set_message("Generating raw chain specification..."); - let spec_name = &output_file - .file_name() - .and_then(|s| s.to_str()) - .unwrap_or(DEFAULT_SPEC_NAME) - .trim_end_matches(".json"); - let raw_spec_name = format!("{spec_name}-raw.json"); - let raw_chain_spec = generate_raw_chain_spec(&binary_path, output_file, &raw_spec_name)?; generated_files.push(format!( "Raw chain specification file generated at: {}", raw_chain_spec.display() @@ -529,6 +508,62 @@ impl BuildSpec { Ok("spec") } + /// Generates chain specification files and returns the paths of the genesis code and genesis + /// state. + /// + /// # Arguments + /// * `cli` - The cli. + pub fn generate_genesis_artifacts( + self, + cli: &mut impl cli::traits::Cli, + ) -> anyhow::Result<(PathBuf, PathBuf)> { + // Ensure binary is built once. + let binary_path = ensure_binary_exists(cli, &self.profile)?; + let spinner = spinner(); + spinner.start("Generating files..."); + + let raw_chain_spec = self.generate_chain_spec(&binary_path, &spinner)?; + + spinner.set_message("Generating genesis code..."); + let wasm_file_name = format!("para-{}.wasm", self.id); + let genesis_code = export_wasm_file(&binary_path, &raw_chain_spec, &wasm_file_name)?; + + spinner.set_message("Generating genesis state..."); + let genesis_file_name = format!("para-{}-genesis-state", self.id); + let genesis_state = + generate_genesis_state_file(&binary_path, &raw_chain_spec, &genesis_file_name)?; + + spinner.stop("Genesis artifacts generated successfully."); + Ok((genesis_code, genesis_state)) + } + + /// Generates plain and raw chain specification files. + fn generate_chain_spec( + &self, + binary_path: &PathBuf, + spinner: &ProgressBar, + ) -> anyhow::Result { + let BuildSpec { output_file, chain, .. } = self; + spinner.start("Generating chain specification..."); + + // Generate plain chain spec. + generate_plain_chain_spec(&binary_path, output_file, self.default_bootnode, chain)?; + // Customize spec based on input. + self.customize()?; + + // Generate raw spec. + spinner.set_message("Generating raw chain specification..."); + let spec_name = &output_file + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or(DEFAULT_SPEC_NAME) + .trim_end_matches(".json"); + let raw_spec_name = format!("{spec_name}-raw.json"); + let raw_chain_spec = generate_raw_chain_spec(&binary_path, output_file, &raw_spec_name)?; + + Ok(raw_chain_spec) + } + // Customize a chain specification. fn customize(&self) -> anyhow::Result<()> { let mut chain_spec = ChainSpec::from(&self.output_file)?; diff --git a/crates/pop-cli/src/commands/up/mod.rs b/crates/pop-cli/src/commands/up/mod.rs index 362de9a9..e2ee19d0 100644 --- a/crates/pop-cli/src/commands/up/mod.rs +++ b/crates/pop-cli/src/commands/up/mod.rs @@ -27,14 +27,14 @@ pub(crate) struct UpArgs { #[arg(value_name = "PATH", index = 1, global = true, conflicts_with = "path")] pub path_pos: Option, - #[command(flatten)] - #[cfg(feature = "contract")] - pub(crate) contract: contract::UpContractCommand, - #[command(flatten)] #[cfg(feature = "parachain")] pub(crate) parachain: parachain::UpParachainCommand, + #[command(flatten)] + #[cfg(feature = "contract")] + pub(crate) contract: contract::UpContractCommand, + #[command(subcommand)] pub(crate) command: Option, } diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index beeca670..14172ec3 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::cli; +use crate::{ + build::spec::BuildSpecCommand, + cli::{self}, +}; use clap::Args; use std::path::PathBuf; @@ -12,12 +15,42 @@ pub struct UpParachainCommand { /// Path to the contract build directory. #[clap(skip)] pub(crate) path: Option, + /// Parachain ID to be used when generating the chain spec files. + #[arg(short, long)] + pub(crate) id: Option, + /// Path to the genesis state file. + #[arg(short = 'G', long = "genesis-state")] + pub(crate) genesis_state: Option, + /// Path to the genesis code file. + #[arg(short = 'C', long = "genesis-code")] + pub(crate) genesis_code: Option, } impl UpParachainCommand { /// Executes the command. pub(crate) async fn execute(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<()> { cli.intro("Deploy a parachain")?; + let para_id = self.id.unwrap_or(reserve_para_id(cli)?); + let (genesis_state, genesis_code) = + match (self.genesis_state.clone(), self.genesis_code.clone()) { + (Some(state), Some(code)) => (state, code), + _ => generate_spec_files(para_id, cli).await?, + }; + cli.outro("Parachain deployment complete.")?; Ok(()) } } + +fn reserve_para_id(cli: &mut impl cli::traits::Cli) -> anyhow::Result { + Ok(2000) +} + +async fn generate_spec_files( + id: u32, + cli: &mut impl cli::traits::Cli, +) -> anyhow::Result<(PathBuf, PathBuf)> { + let build_spec = BuildSpecCommand { id: Some(id), ..Default::default() } + .configure_build_spec(cli) + .await?; + build_spec.generate_genesis_artifacts(cli) +} From 80162728af32ded215df6acb1de8102acb1b8ea1 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 11 Feb 2025 10:49:16 +0100 Subject: [PATCH 03/27] feat: logic to interact with a chain --- crates/pop-cli/src/commands/call/chain.rs | 8 +- crates/pop-cli/src/commands/up/parachain.rs | 84 ++++++++++++++++++++- 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 18f61fe7..45a9a334 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -358,13 +358,13 @@ impl CallChainCommand { } // Represents a chain, including its URL, client connection, and available pallets. -struct Chain { +pub struct Chain { // Websocket endpoint of the node. - url: Url, + pub(crate) url: Url, // The client used to interact with the chain. - client: OnlineClient, + pub(crate) client: OnlineClient, // A list of pallets available on the chain. - pallets: Vec, + pub(crate) pallets: Vec, } /// Represents a configured dispatchable function call, including the pallet, function, arguments, diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 14172ec3..187b2e70 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -2,11 +2,21 @@ use crate::{ build::spec::BuildSpecCommand, - cli::{self}, + call::chain::Chain, + cli::{self, traits::*}, + common::wallet::request_signature, }; +use anyhow::{anyhow, Result}; use clap::Args; +use pop_parachains::{ + construct_extrinsic, find_dispatchable_by_name, parse_chain_metadata, set_up_client, + submit_signed_extrinsic, Action, OnlineClient, Payload, SubstrateConfig, +}; + use std::path::PathBuf; +use url::Url; +const DEFAULT_URL: &str = "wss://paseo.rpc.amforc.com/"; const HELP_HEADER: &str = "Parachain deployment options"; #[derive(Args, Clone, Default)] @@ -24,13 +34,17 @@ pub struct UpParachainCommand { /// Path to the genesis code file. #[arg(short = 'C', long = "genesis-code")] pub(crate) genesis_code: Option, + /// Websocket endpoint of the relay chain. + #[arg(long)] + pub(crate) relay_url: Option, } impl UpParachainCommand { /// Executes the command. - pub(crate) async fn execute(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<()> { + pub(crate) async fn execute(self, cli: &mut impl Cli) -> Result<()> { cli.intro("Deploy a parachain")?; - let para_id = self.id.unwrap_or(reserve_para_id(cli)?); + let chain = self.configure_chain(cli).await?; + let para_id = self.id.unwrap_or(reserve_para_id(chain, cli).await?); let (genesis_state, genesis_code) = match (self.genesis_state.clone(), self.genesis_code.clone()) { (Some(state), Some(code)) => (state, code), @@ -39,9 +53,44 @@ impl UpParachainCommand { cli.outro("Parachain deployment complete.")?; Ok(()) } + + // Configures the chain by resolving the URL and fetching its metadata. + async fn configure_chain(&self, cli: &mut impl Cli) -> Result { + // Resolve url. + let url = match &self.relay_url { + Some(url) => url.clone(), + None => { + // Prompt for url. + let url: String = cli + .input("Enter the relay chain node URL to deploy your parachain:") + .default_input(DEFAULT_URL) + .interact()?; + Url::parse(&url)? + }, + }; + + // Parse metadata from chain url. + let client = set_up_client(url.as_str()).await?; + let mut pallets = parse_chain_metadata(&client).map_err(|e| { + anyhow!(format!("Unable to fetch the chain metadata: {}", e.to_string())) + })?; + // Sort by name for display. + pallets.sort_by(|a, b| a.name.cmp(&b.name)); + pallets.iter_mut().for_each(|p| p.functions.sort_by(|a, b| a.name.cmp(&b.name))); + Ok(Chain { url, client, pallets }) + } } -fn reserve_para_id(cli: &mut impl cli::traits::Cli) -> anyhow::Result { +async fn reserve_para_id(chain: Chain, cli: &mut impl Cli) -> Result { + cli.info("Reserving a parachain ID for your parachain...")?; + let ex = find_dispatchable_by_name( + &chain.pallets, + Action::Reserve.pallet_name(), + Action::Reserve.function_name(), + )?; + let xt = construct_extrinsic(ex, Vec::new())?; + let call_data = xt.encode_call_data(&chain.client.metadata())?; + submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli).await?; Ok(2000) } @@ -49,8 +98,35 @@ async fn generate_spec_files( id: u32, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result<(PathBuf, PathBuf)> { + cli.info("Generating the chain spec for your parachain—some extra information is needed:")?; let build_spec = BuildSpecCommand { id: Some(id), ..Default::default() } .configure_build_spec(cli) .await?; build_spec.generate_genesis_artifacts(cli) } + +// Sign and submit an extrinsic using wallet integration. +pub async fn submit_extrinsic_with_wallet( + client: &OnlineClient, + url: &Url, + call_data: Vec, + cli: &mut impl Cli, +) -> Result<()> { + let maybe_payload = request_signature(call_data, url.to_string()).await?; + if let Some(payload) = maybe_payload { + cli.success("Signed payload received.")?; + let spinner = cliclack::spinner(); + spinner.start( + "Submitting the extrinsic and then waiting for finalization, please be patient...", + ); + + let result = submit_signed_extrinsic(client.clone(), payload) + .await + .map_err(|err| anyhow!("{}", format!("{err:?}")))?; + + spinner.stop(format!("Extrinsic submitted with hash: {:?}", result)); + } else { + cli.outro_cancel("No signed payload received.")?; + } + Ok(()) +} From 11869aad99dc69b60e12d927abd24994c3eefc8c Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 11 Feb 2025 16:05:53 +0100 Subject: [PATCH 04/27] feat: register parachain --- crates/pop-cli/src/commands/call/chain.rs | 2 +- crates/pop-cli/src/commands/up/parachain.rs | 68 ++++++++++----------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 45a9a334..019ba399 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -474,7 +474,7 @@ impl Call { } // Sign and submit an extrinsic using wallet integration. -async fn submit_extrinsic_with_wallet( +pub async fn submit_extrinsic_with_wallet( client: &OnlineClient, url: &Url, call_data: Vec, diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 187b2e70..e65c954f 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -2,15 +2,14 @@ use crate::{ build::spec::BuildSpecCommand, - call::chain::Chain, + call::chain::{submit_extrinsic_with_wallet, Chain}, cli::{self, traits::*}, - common::wallet::request_signature, }; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ construct_extrinsic, find_dispatchable_by_name, parse_chain_metadata, set_up_client, - submit_signed_extrinsic, Action, OnlineClient, Payload, SubstrateConfig, + Action, Payload, }; use std::path::PathBuf; @@ -22,7 +21,7 @@ const HELP_HEADER: &str = "Parachain deployment options"; #[derive(Args, Clone, Default)] #[clap(next_help_heading = HELP_HEADER)] pub struct UpParachainCommand { - /// Path to the contract build directory. + /// Path to the chain directory. #[clap(skip)] pub(crate) path: Option, /// Parachain ID to be used when generating the chain spec files. @@ -44,12 +43,17 @@ impl UpParachainCommand { pub(crate) async fn execute(self, cli: &mut impl Cli) -> Result<()> { cli.intro("Deploy a parachain")?; let chain = self.configure_chain(cli).await?; - let para_id = self.id.unwrap_or(reserve_para_id(chain, cli).await?); + let para_id = if let Some(id) = self.id { + id + } else { + reserve_para_id(&chain, cli).await? + }; let (genesis_state, genesis_code) = match (self.genesis_state.clone(), self.genesis_code.clone()) { (Some(state), Some(code)) => (state, code), - _ => generate_spec_files(para_id, cli).await?, + _ => generate_spec_files(para_id, self.path, cli).await?, }; + register_parachain(&chain, para_id, genesis_state, genesis_code, cli).await?; cli.outro("Parachain deployment complete.")?; Ok(()) } @@ -71,17 +75,14 @@ impl UpParachainCommand { // Parse metadata from chain url. let client = set_up_client(url.as_str()).await?; - let mut pallets = parse_chain_metadata(&client).map_err(|e| { + let pallets = parse_chain_metadata(&client).map_err(|e| { anyhow!(format!("Unable to fetch the chain metadata: {}", e.to_string())) })?; - // Sort by name for display. - pallets.sort_by(|a, b| a.name.cmp(&b.name)); - pallets.iter_mut().for_each(|p| p.functions.sort_by(|a, b| a.name.cmp(&b.name))); Ok(Chain { url, client, pallets }) } } -async fn reserve_para_id(chain: Chain, cli: &mut impl Cli) -> Result { +async fn reserve_para_id(chain: &Chain, cli: &mut impl Cli) -> Result { cli.info("Reserving a parachain ID for your parachain...")?; let ex = find_dispatchable_by_name( &chain.pallets, @@ -96,37 +97,32 @@ async fn reserve_para_id(chain: Chain, cli: &mut impl Cli) -> Result { async fn generate_spec_files( id: u32, + path: Option, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result<(PathBuf, PathBuf)> { - cli.info("Generating the chain spec for your parachain—some extra information is needed:")?; + cli.info("Generating the chain spec for your parachain, some extra information is needed:")?; + if let Some(path) = &path { + std::env::set_current_dir(path).map_err(|err| { + anyhow!("Failed to change working directory to {}: {}", path.display(), err) + })?; + } let build_spec = BuildSpecCommand { id: Some(id), ..Default::default() } .configure_build_spec(cli) .await?; build_spec.generate_genesis_artifacts(cli) } -// Sign and submit an extrinsic using wallet integration. -pub async fn submit_extrinsic_with_wallet( - client: &OnlineClient, - url: &Url, - call_data: Vec, - cli: &mut impl Cli, -) -> Result<()> { - let maybe_payload = request_signature(call_data, url.to_string()).await?; - if let Some(payload) = maybe_payload { - cli.success("Signed payload received.")?; - let spinner = cliclack::spinner(); - spinner.start( - "Submitting the extrinsic and then waiting for finalization, please be patient...", - ); - - let result = submit_signed_extrinsic(client.clone(), payload) - .await - .map_err(|err| anyhow!("{}", format!("{err:?}")))?; - - spinner.stop(format!("Extrinsic submitted with hash: {:?}", result)); - } else { - cli.outro_cancel("No signed payload received.")?; - } +async fn register_parachain(chain: &Chain, id: u32, genesis_state: PathBuf, genesis_code: PathBuf, cli: &mut impl Cli) -> Result<()> { + cli.info("Registering a parachain ID")?; + let ex = find_dispatchable_by_name( + &chain.pallets, + Action::Register.pallet_name(), + Action::Register.function_name(), + )?; + let state = std::fs::read_to_string(genesis_state).map_err(|err| anyhow!("Failed to read genesis state file: {}", err.to_string()))?; + let code = std::fs::read_to_string(genesis_code).map_err(|err| anyhow!("Failed to read genesis state file: {}", err.to_string()))?; + let xt = construct_extrinsic(ex, vec![id.to_string(), state, code])?; + let call_data = xt.encode_call_data(&chain.client.metadata())?; + submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli).await?; Ok(()) -} +} \ No newline at end of file From d30c135d0a8a08455f83d06f4773e002289c539a Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 11 Feb 2025 17:54:33 +0100 Subject: [PATCH 05/27] refactor: clean code and improve docs --- crates/pop-cli/src/commands/build/spec.rs | 2 +- crates/pop-cli/src/commands/up/parachain.rs | 133 ++++++++++++++------ 2 files changed, 98 insertions(+), 37 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index c09220bf..2b580f19 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -534,7 +534,7 @@ impl BuildSpec { generate_genesis_state_file(&binary_path, &raw_chain_spec, &genesis_file_name)?; spinner.stop("Genesis artifacts generated successfully."); - Ok((genesis_code, genesis_state)) + Ok((genesis_state, genesis_code)) } /// Generates plain and raw chain specification files. diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index e65c954f..1b17bbbf 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -1,15 +1,15 @@ // SPDX-License-Identifier: GPL-3.0 use crate::{ - build::spec::BuildSpecCommand, + build::spec::{BuildSpec, BuildSpecCommand}, call::chain::{submit_extrinsic_with_wallet, Chain}, cli::{self, traits::*}, }; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, find_dispatchable_by_name, parse_chain_metadata, set_up_client, - Action, Payload, + construct_extrinsic, find_dispatchable_by_name, parse_chain_metadata, set_up_client, Action, + Payload, }; use std::path::PathBuf; @@ -24,7 +24,7 @@ pub struct UpParachainCommand { /// Path to the chain directory. #[clap(skip)] pub(crate) path: Option, - /// Parachain ID to be used when generating the chain spec files. + /// Parachain ID to use. If not specified, a new ID will be reserved. #[arg(short, long)] pub(crate) id: Option, /// Path to the genesis state file. @@ -43,17 +43,40 @@ impl UpParachainCommand { pub(crate) async fn execute(self, cli: &mut impl Cli) -> Result<()> { cli.intro("Deploy a parachain")?; let chain = self.configure_chain(cli).await?; - let para_id = if let Some(id) = self.id { - id - } else { - reserve_para_id(&chain, cli).await? - }; + let para_id = match self.id { + Some(id) => id, + None => { + cli.info("Reserving a parachain ID...")?; + match reserve_para_id(&chain, cli).await { + Ok(id) => id, + Err(e) => { + cli.outro_cancel(&format!("Failed to reserve parachain ID: {}", e))?; + return Err(e); + }, + } + }, + }; let (genesis_state, genesis_code) = match (self.genesis_state.clone(), self.genesis_code.clone()) { (Some(state), Some(code)) => (state, code), - _ => generate_spec_files(para_id, self.path, cli).await?, + _ => { + cli.info("Generating the chain spec for your parachain, some extra information is needed:")?; + match generate_spec_files(para_id, self.path, cli).await { + Ok(files) => files, + Err(e) => { + cli.outro_cancel(&format!("Failed to generate spec files: {}", e))?; + return Err(e); + }, + } + }, }; - register_parachain(&chain, para_id, genesis_state, genesis_code, cli).await?; + cli.info("Registering a parachain ID")?; + if let Err(e) = register_parachain(&chain, para_id, genesis_state, genesis_code, cli).await + { + cli.outro_cancel(&format!("Failed to register parachain: {}", e))?; + return Err(e); + } + cli.outro("Parachain deployment complete.")?; Ok(()) } @@ -82,47 +105,85 @@ impl UpParachainCommand { } } +/// Reserves a parachain ID by submitting an extrinsic. async fn reserve_para_id(chain: &Chain, cli: &mut impl Cli) -> Result { - cli.info("Reserving a parachain ID for your parachain...")?; - let ex = find_dispatchable_by_name( + let call_data = prepare_reserve_para_id_extrinsic(chain)?; + submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli).await?; + Ok(2000) +} + +/// Constructs an extrinsic for reserving a parachain ID. +fn prepare_reserve_para_id_extrinsic(chain: &Chain) -> Result> { + let function = find_dispatchable_by_name( &chain.pallets, Action::Reserve.pallet_name(), Action::Reserve.function_name(), )?; - let xt = construct_extrinsic(ex, Vec::new())?; - let call_data = xt.encode_call_data(&chain.client.metadata())?; - submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli).await?; - Ok(2000) + let xt = construct_extrinsic(function, Vec::new())?; + Ok(xt.encode_call_data(&chain.client.metadata())?) } +/// Generates chain spec files for the parachain. async fn generate_spec_files( id: u32, - path: Option, - cli: &mut impl cli::traits::Cli, + path: Option, + cli: &mut impl Cli, ) -> anyhow::Result<(PathBuf, PathBuf)> { - cli.info("Generating the chain spec for your parachain, some extra information is needed:")?; - if let Some(path) = &path { - std::env::set_current_dir(path).map_err(|err| { - anyhow!("Failed to change working directory to {}: {}", path.display(), err) - })?; - } - let build_spec = BuildSpecCommand { id: Some(id), ..Default::default() } - .configure_build_spec(cli) - .await?; + change_working_directory(&path)?; + let build_spec = configure_build_spec(id, cli).await?; build_spec.generate_genesis_artifacts(cli) } -async fn register_parachain(chain: &Chain, id: u32, genesis_state: PathBuf, genesis_code: PathBuf, cli: &mut impl Cli) -> Result<()> { - cli.info("Registering a parachain ID")?; +/// Changes the working directory if a path is provided, ensuring the build spec process runs in the +/// correct context. +fn change_working_directory(path: &Option) -> Result<()> { + if let Some(path) = path { + std::env::set_current_dir(path)?; + } + Ok(()) +} + +/// Configures the chain specification requirements. +async fn configure_build_spec(id: u32, cli: &mut impl Cli) -> Result { + Ok(BuildSpecCommand { + id: Some(id), + genesis_code: true, + genesis_state: true, + ..Default::default() + } + .configure_build_spec(cli) + .await?) +} + +/// Registers a parachain by submitting an extrinsic. +async fn register_parachain( + chain: &Chain, + id: u32, + genesis_state: PathBuf, + genesis_code: PathBuf, + cli: &mut impl Cli, +) -> Result<()> { + let call_data = prepare_register_parachain_extrinsic(chain, id, genesis_state, genesis_code)?; + submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli).await?; + Ok(()) +} + +/// Constructs an extrinsic for registering a parachain. +fn prepare_register_parachain_extrinsic( + chain: &Chain, + id: u32, + genesis_state: PathBuf, + genesis_code: PathBuf, +) -> Result> { let ex = find_dispatchable_by_name( &chain.pallets, Action::Register.pallet_name(), Action::Register.function_name(), )?; - let state = std::fs::read_to_string(genesis_state).map_err(|err| anyhow!("Failed to read genesis state file: {}", err.to_string()))?; - let code = std::fs::read_to_string(genesis_code).map_err(|err| anyhow!("Failed to read genesis state file: {}", err.to_string()))?; + let state = std::fs::read_to_string(genesis_state) + .map_err(|err| anyhow!("Failed to read genesis state file: {}", err.to_string()))?; + let code = std::fs::read_to_string(genesis_code) + .map_err(|err| anyhow!("Failed to read genesis state file: {}", err.to_string()))?; let xt = construct_extrinsic(ex, vec![id.to_string(), state, code])?; - let call_data = xt.encode_call_data(&chain.client.metadata())?; - submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli).await?; - Ok(()) -} \ No newline at end of file + Ok(xt.encode_call_data(&chain.client.metadata())?) +} From 9bd7229a85381f651bca8032848c6e29b8cb2e69 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 11 Feb 2025 20:30:26 +0100 Subject: [PATCH 06/27] test: unit tests for pop up methods --- crates/pop-cli/src/commands/up/parachain.rs | 153 +++++++++++++++++- .../pop-parachains/src/call/metadata/mod.rs | 5 +- 2 files changed, 155 insertions(+), 3 deletions(-) diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 1b17bbbf..32ce9100 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -3,7 +3,7 @@ use crate::{ build::spec::{BuildSpec, BuildSpecCommand}, call::chain::{submit_extrinsic_with_wallet, Chain}, - cli::{self, traits::*}, + cli::traits::*, }; use anyhow::{anyhow, Result}; use clap::Args; @@ -89,7 +89,7 @@ impl UpParachainCommand { None => { // Prompt for url. let url: String = cli - .input("Enter the relay chain node URL to deploy your parachain:") + .input("Enter the relay chain node URL to deploy your parachain") .default_input(DEFAULT_URL) .interact()?; Url::parse(&url)? @@ -187,3 +187,152 @@ fn prepare_register_parachain_extrinsic( let xt = construct_extrinsic(ex, vec![id.to_string(), state, code])?; Ok(xt.encode_call_data(&chain.client.metadata())?) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + build::spec::{ChainType, RelayChain}, + cli::MockCli, + }; + use pop_common::Profile; + use pop_parachains::decode_call_data; + use std::{env, fs}; + use strum::{EnumMessage, VariantArray}; + use tempfile::tempdir; + use url::Url; + + const POLKADOT_NETWORK_URL: &str = "wss://polkadot-rpc.publicnode.com"; + + #[tokio::test] + async fn configure_chain_works() -> Result<()> { + let mut cli = MockCli::new().expect_input( + "Enter the relay chain node URL to deploy your parachain", + POLKADOT_NETWORK_URL.into(), + ); + let chain = UpParachainCommand::default().configure_chain(&mut cli).await?; + assert_eq!(chain.url, Url::parse(POLKADOT_NETWORK_URL)?); + cli.verify() + } + + #[tokio::test] + async fn prepare_reserve_para_id_extrinsic_works() -> Result<()> { + let mut cli = MockCli::new(); + let chain = UpParachainCommand { + relay_url: Some(Url::parse(POLKADOT_NETWORK_URL)?), + ..Default::default() + } + .configure_chain(&mut cli) + .await?; + let call_data = prepare_reserve_para_id_extrinsic(&chain)?; + assert_eq!(call_data, decode_call_data("0x4605")?); + Ok(()) + } + + #[tokio::test] + async fn change_working_directory_works() -> Result<()> { + let temp_dir = tempdir()?; + let my_parachain_path = Some(temp_dir.path().to_path_buf()); + change_working_directory(&my_parachain_path)?; + assert_eq!(fs::canonicalize(env::current_dir()?)?, fs::canonicalize(temp_dir.path())?); + + change_working_directory(&None)?; + assert_eq!(fs::canonicalize(env::current_dir()?)?, fs::canonicalize(temp_dir.path())?); + Ok(()) + } + + #[tokio::test] + async fn prepare_register_parachain_extrinsic_works() -> Result<()> { + let mut cli = MockCli::new(); + let chain = UpParachainCommand { + relay_url: Some(Url::parse(POLKADOT_NETWORK_URL)?), + ..Default::default() + } + .configure_chain(&mut cli) + .await?; + // Create a temporary files to act as genesis_state and genesis_code files. + let temp_dir = tempdir()?; + let genesis_state_path = temp_dir.path().join("genesis_state"); + let genesis_code_path = temp_dir.path().join("genesis_code.wasm"); + std::fs::write(&genesis_state_path, "0x1234")?; + std::fs::write(&genesis_code_path, "0x1234")?; + + let call_data = prepare_register_parachain_extrinsic( + &chain, + 2000, + genesis_state_path, + genesis_code_path, + )?; + assert_eq!(call_data, decode_call_data("0x4600d0070000081234081234")?); + Ok(()) + } + + #[tokio::test] + async fn configure_build_spec_works() -> Result<()> { + let mut cli = MockCli::new().expect_input("Provide the chain specification to use (e.g. dev, local, custom or a path to an existing file)", "dev".to_string()) + .expect_input( + "Name or path for the plain chain spec file:", "output_file".to_string()) + .expect_input( + "Enter the protocol ID that will identify your network:", "protocol_id".to_string()) + .expect_select( + "Choose the chain type: ", + Some(false), + true, + Some(chain_types()), + ChainType::Development as usize, + ).expect_select( + "Choose the relay your chain will be connecting to: ", + Some(false), + true, + Some(relays()), + RelayChain::PaseoLocal as usize, + ).expect_select( + "Choose the build profile of the binary that should be used: ", + Some(false), + true, + Some(profiles()), + Profile::Release as usize, + ); + + configure_build_spec(2000, &mut cli).await?; + cli.verify()?; + Ok(()) + } + + + fn relays() -> Vec<(String, String)> { + RelayChain::VARIANTS + .iter() + .map(|variant| { + ( + variant.get_message().unwrap_or(variant.as_ref()).into(), + variant.get_detailed_message().unwrap_or_default().into(), + ) + }) + .collect() + } + + fn chain_types() -> Vec<(String, String)> { + ChainType::VARIANTS + .iter() + .map(|variant| { + ( + variant.get_message().unwrap_or(variant.as_ref()).into(), + variant.get_detailed_message().unwrap_or_default().into(), + ) + }) + .collect() + } + + fn profiles() -> Vec<(String, String)> { + Profile::VARIANTS + .iter() + .map(|variant| { + ( + variant.get_message().unwrap_or(variant.as_ref()).into(), + variant.get_detailed_message().unwrap_or_default().into(), + ) + }) + .collect() + } +} diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 5a9a9a2c..2d371424 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -188,7 +188,10 @@ pub fn parse_dispatchable_arguments( .add_custom_parser(custom_parsers::parse_ss58) .parse(&processed_param) .0 - .map_err(|_| Error::ParamProcessingError) + .map_err(|_| { + eprintln!("Failed to parse parameter: {}", processed_param); + Error::ParamProcessingError + }) }) .collect() } From 59525803a1f07b7ec08d1f1ec3e233d5381fc9dc Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 12 Feb 2025 15:39:25 +0100 Subject: [PATCH 07/27] refactor: small fixes with visibility and removing logs --- crates/pop-cli/src/commands/build/spec.rs | 10 +++++----- crates/pop-cli/src/commands/call/chain.rs | 4 ++-- crates/pop-cli/src/commands/up/parachain.rs | 1 - crates/pop-parachains/src/call/metadata/mod.rs | 5 +---- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 2b580f19..b1b84a8e 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -193,7 +193,7 @@ impl BuildSpecCommand { /// /// # Arguments /// * `cli` - The cli. - pub async fn configure_build_spec( + pub(crate) async fn configure_build_spec( self, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { @@ -439,7 +439,7 @@ impl BuildSpecCommand { // Represents the configuration for building a chain specification. #[derive(Debug)] -pub struct BuildSpec { +pub(crate) struct BuildSpec { output_file: PathBuf, profile: Profile, id: u32, @@ -508,12 +508,12 @@ impl BuildSpec { Ok("spec") } - /// Generates chain specification files and returns the paths of the genesis code and genesis - /// state. + /// Generates chain specification files and returns the file paths for the generated genesis + /// code and genesis state files. /// /// # Arguments /// * `cli` - The cli. - pub fn generate_genesis_artifacts( + pub(crate) fn generate_genesis_artifacts( self, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result<(PathBuf, PathBuf)> { diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 019ba399..8698dc31 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -358,7 +358,7 @@ impl CallChainCommand { } // Represents a chain, including its URL, client connection, and available pallets. -pub struct Chain { +pub(crate) struct Chain { // Websocket endpoint of the node. pub(crate) url: Url, // The client used to interact with the chain. @@ -474,7 +474,7 @@ impl Call { } // Sign and submit an extrinsic using wallet integration. -pub async fn submit_extrinsic_with_wallet( +pub(crate) async fn submit_extrinsic_with_wallet( client: &OnlineClient, url: &Url, call_data: Vec, diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 32ce9100..24424d12 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -299,7 +299,6 @@ mod tests { Ok(()) } - fn relays() -> Vec<(String, String)> { RelayChain::VARIANTS .iter() diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 2d371424..5a9a9a2c 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -188,10 +188,7 @@ pub fn parse_dispatchable_arguments( .add_custom_parser(custom_parsers::parse_ss58) .parse(&processed_param) .0 - .map_err(|_| { - eprintln!("Failed to parse parameter: {}", processed_param); - Error::ParamProcessingError - }) + .map_err(|_| Error::ParamProcessingError) }) .collect() } From d5bd2987d19088f0afdc19ebb98dffdfd55e89b9 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 13 Feb 2025 09:28:07 +0100 Subject: [PATCH 08/27] feat: return events in submit_signed_extrinsic --- crates/pop-cli/src/commands/call/chain.rs | 17 ++++++++++------- crates/pop-cli/src/commands/up/parachain.rs | 2 +- crates/pop-parachains/src/call/mod.rs | 8 ++++---- crates/pop-parachains/src/lib.rs | 1 + 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 8698dc31..2c636fe0 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -12,7 +12,8 @@ use pop_parachains::{ construct_extrinsic, construct_sudo_extrinsic, decode_call_data, encode_call_data, find_dispatchable_by_name, find_pallet_by_name, parse_chain_metadata, set_up_client, sign_and_submit_extrinsic, submit_signed_extrinsic, supported_actions, Action, CallData, - DynamicPayload, Function, OnlineClient, Pallet, Param, Payload, SubstrateConfig, + DynamicPayload, ExtrinsicEvents, Function, OnlineClient, Pallet, Param, Payload, + SubstrateConfig, }; use url::Url; @@ -109,7 +110,9 @@ impl CallChainCommand { // Sign and submit the extrinsic. let result = if self.use_wallet { let call_data = xt.encode_call_data(&chain.client.metadata())?; - submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, &mut cli).await + submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, &mut cli) + .await?; + Ok(()) } else { call.submit_extrinsic(&chain.client, &chain.url, xt, &mut cli).await }; @@ -473,13 +476,13 @@ impl Call { } } -// Sign and submit an extrinsic using wallet integration. +// Sign and submit an extrinsic using wallet integration, then returns the resulting events. pub(crate) async fn submit_extrinsic_with_wallet( client: &OnlineClient, url: &Url, call_data: Vec, cli: &mut impl Cli, -) -> Result<()> { +) -> Result> { let maybe_payload = request_signature(call_data, url.to_string()).await?; if let Some(payload) = maybe_payload { cli.success("Signed payload received.")?; @@ -492,11 +495,11 @@ pub(crate) async fn submit_extrinsic_with_wallet( .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; - spinner.stop(format!("Extrinsic submitted with hash: {:?}", result)); + spinner.stop(format!("Extrinsic submitted with hash: {:?}", result.extrinsic_hash())); + Ok(result) } else { - display_message("No signed payload received.", false, cli)?; + return Err(anyhow!("No signed payload received.")); } - Ok(()) } // Displays a message to the user, with formatting based on the success status. diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 24424d12..1d94cb07 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -108,7 +108,7 @@ impl UpParachainCommand { /// Reserves a parachain ID by submitting an extrinsic. async fn reserve_para_id(chain: &Chain, cli: &mut impl Cli) -> Result { let call_data = prepare_reserve_para_id_extrinsic(chain)?; - submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli).await?; + let events = submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli).await?; Ok(2000) } diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 4569687e..95a339d0 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -7,6 +7,7 @@ use pop_common::{ }; use sp_core::bytes::{from_hex, to_hex}; use subxt::{ + blocks::ExtrinsicEvents, dynamic::Value, tx::{DynamicPayload, Payload, SubmittableExtrinsic}, OnlineClient, SubstrateConfig, @@ -90,18 +91,17 @@ pub async fn sign_and_submit_extrinsic( pub async fn submit_signed_extrinsic( client: OnlineClient, payload: String, -) -> Result { +) -> Result, Error> { let hex_encoded = from_hex(&payload).map_err(|e| Error::CallDataDecodingError(e.to_string()))?; let extrinsic = SubmittableExtrinsic::from_bytes(client, hex_encoded); - let result = extrinsic + Ok(extrinsic .submit_and_watch() .await .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))? .wait_for_finalized_success() .await - .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?; - Ok(format!("{:?}", result.extrinsic_hash())) + .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?) } /// Encodes the call data for a given extrinsic into a hexadecimal string. diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 50bb6af5..fce91185 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -34,6 +34,7 @@ pub use new_parachain::instantiate_template_dir; pub use relay::{clear_dmpq, RelayChain}; // External export from subxt. pub use subxt::{ + blocks::ExtrinsicEvents, tx::{DynamicPayload, Payload}, OnlineClient, SubstrateConfig, }; From 6abeba842b051e09f15f78298fc06d40851b99fe Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 14 Feb 2025 12:41:07 +0100 Subject: [PATCH 09/27] feat: get para_id from event --- Cargo.lock | 1 + Cargo.toml | 1 + crates/pop-cli/src/commands/up/parachain.rs | 24 ++++++++----- crates/pop-parachains/Cargo.toml | 1 + .../src/call/metadata/events.rs | 35 +++++++++++++++++++ .../pop-parachains/src/call/metadata/mod.rs | 1 + crates/pop-parachains/src/errors.rs | 3 ++ crates/pop-parachains/src/lib.rs | 1 + 8 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 crates/pop-parachains/src/call/metadata/events.rs diff --git a/Cargo.lock b/Cargo.lock index f3a264fc..ffffe857 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9449,6 +9449,7 @@ dependencies = [ "duct", "glob", "indexmap 2.7.0", + "parity-scale-codec", "pop-common", "scale-info", "scale-value", diff --git a/Cargo.toml b/Cargo.toml index 07be639c..37b62a14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ subxt = "0.38.0" ink_env = "5.0.0" sp-core = "32.0.0" sp-weights = "31.0.0" +scale = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } scale-info = { version = "2.11.4", default-features = false, features = ["derive"] } scale-value = { version = "0.17.0", default-features = false, features = ["from-string", "parser-ss58"] } contract-build = "5.0.2" diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 1d94cb07..4c67f743 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -8,8 +8,8 @@ use crate::{ use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, find_dispatchable_by_name, parse_chain_metadata, set_up_client, Action, - Payload, + construct_extrinsic, extract_para_id_from_event, find_dispatchable_by_name, + parse_chain_metadata, set_up_client, Action, Payload, }; use std::path::PathBuf; @@ -50,8 +50,8 @@ impl UpParachainCommand { match reserve_para_id(&chain, cli).await { Ok(id) => id, Err(e) => { - cli.outro_cancel(&format!("Failed to reserve parachain ID: {}", e))?; - return Err(e); + cli.outro_cancel(&format!("{}", e))?; + return Ok(()); }, } }, @@ -60,12 +60,12 @@ impl UpParachainCommand { match (self.genesis_state.clone(), self.genesis_code.clone()) { (Some(state), Some(code)) => (state, code), _ => { - cli.info("Generating the chain spec for your parachain, some extra information is needed:")?; + cli.info("Generating the chain spec for your parachain.")?; match generate_spec_files(para_id, self.path, cli).await { Ok(files) => files, Err(e) => { cli.outro_cancel(&format!("Failed to generate spec files: {}", e))?; - return Err(e); + return Ok(()); }, } }, @@ -74,7 +74,7 @@ impl UpParachainCommand { if let Err(e) = register_parachain(&chain, para_id, genesis_state, genesis_code, cli).await { cli.outro_cancel(&format!("Failed to register parachain: {}", e))?; - return Err(e); + return Ok(()); } cli.outro("Parachain deployment complete.")?; @@ -108,8 +108,14 @@ impl UpParachainCommand { /// Reserves a parachain ID by submitting an extrinsic. async fn reserve_para_id(chain: &Chain, cli: &mut impl Cli) -> Result { let call_data = prepare_reserve_para_id_extrinsic(chain)?; - let events = submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli).await?; - Ok(2000) + let events = submit_extrinsic_with_wallet(&chain.client.clone(), &chain.url, call_data, cli) + .await + .map_err(|e| anyhow::anyhow!("Parachain ID reservation failed: {}", e))?; + let para_id = extract_para_id_from_event(&events).await.map_err(|_| { + anyhow::anyhow!("Unable to parse the event. Specify the parachain ID manually with --id.") + })?; + cli.success(format!("Successfully reserved parachain ID: {}", para_id))?; + Ok(para_id) } /// Constructs an extrinsic for reserving a parachain ID. diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 619b3336..a7bd5b4d 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -25,6 +25,7 @@ url.workspace = true askama.workspace = true indexmap.workspace = true +scale.workspace = true scale-info.workspace = true scale-value.workspace = true sp-core.workspace = true diff --git a/crates/pop-parachains/src/call/metadata/events.rs b/crates/pop-parachains/src/call/metadata/events.rs new file mode 100644 index 00000000..f15957bc --- /dev/null +++ b/crates/pop-parachains/src/call/metadata/events.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0 + +use scale::{Decode, Encode}; +use subxt::{ + blocks::ExtrinsicEvents, + events::StaticEvent, + ext::{scale_decode::DecodeAsType, scale_encode::EncodeAsType}, + SubstrateConfig, +}; + +use crate::Error; + +#[derive(Debug, Encode, Decode, DecodeAsType, EncodeAsType)] +#[decode_as_type(crate_path = "subxt::ext::scale_decode")] +#[encode_as_type(crate_path = "subxt::ext::scale_encode")] +pub struct Reserved { + pub para_id: u32, +} +impl StaticEvent for Reserved { + const PALLET: &'static str = "Registrar"; + const EVENT: &'static str = "Reserved"; +} + +/// Extracts the `para_id` field from a `Reserved` event. +/// +/// # Arguments +/// * `events` - The extrinsic events from a transaction. +pub async fn extract_para_id_from_event( + events: &ExtrinsicEvents, +) -> Result { + let reserved_event = events.find_first::()?; + reserved_event + .map(|event| event.para_id) + .ok_or(Error::EventNotFound("Reserved".to_string())) +} diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 5a9a9a2c..38f0bfaa 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -7,6 +7,7 @@ use std::fmt::{Display, Formatter}; use subxt::{dynamic::Value, utils::to_hex, Metadata, OnlineClient, SubstrateConfig}; pub mod action; +pub mod events; pub mod params; /// Represents a pallet in the blockchain, including its dispatchable functions. diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 2dd8805a..0288c441 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -27,6 +27,9 @@ pub enum Error { CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, + /// The specified event was not found in the extrinsic events. + #[error("Event {0} not found.")] + EventNotFound(String), /// An error occurred during the submission of an extrinsic. #[error("Extrinsic submission error: {0}")] ExtrinsicSubmissionError(String), diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index fce91185..9e71dcbf 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -21,6 +21,7 @@ pub use call::{ construct_extrinsic, construct_sudo_extrinsic, decode_call_data, encode_call_data, metadata::{ action::{supported_actions, Action}, + events::extract_para_id_from_event, find_dispatchable_by_name, find_pallet_by_name, params::Param, parse_chain_metadata, Function, Pallet, From d360dba4bde664fdcf9312d1d435dac0e448cb43 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 17 Feb 2025 18:30:39 +0100 Subject: [PATCH 10/27] test: fix detects_parachain_correctly --- crates/pop-cli/src/commands/up/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/pop-cli/src/commands/up/mod.rs b/crates/pop-cli/src/commands/up/mod.rs index e2ee19d0..302b181b 100644 --- a/crates/pop-cli/src/commands/up/mod.rs +++ b/crates/pop-cli/src/commands/up/mod.rs @@ -147,7 +147,8 @@ mod tests { async fn detects_parachain_correctly() -> anyhow::Result<()> { let temp_dir = tempfile::tempdir()?; let name = "parachain"; - let project_path = temp_dir.path().join(name); + let path = temp_dir.path(); + let project_path = path.join(name); let config = Config { symbol: "DOT".to_string(), decimals: 18, @@ -155,9 +156,12 @@ mod tests { }; instantiate_template_dir(&Parachain::Standard, &project_path, None, config)?; - let args = create_up_args(project_path)?; - let mut cli = - MockCli::new().expect_warning("Deploy a parachain"); + let mut args = create_up_args(project_path)?; + args.parachain.relay_url = Some(Url::parse("wss://polkadot-rpc.publicnode.com")?); + args.parachain.id = Some(2000); + args.parachain.genesis_code = Some(PathBuf::from("path/to/genesis")); + args.parachain.genesis_state = Some(PathBuf::from("path/to/state")); + let mut cli = MockCli::new(); assert_eq!(Command::execute_project_deployment(args, &mut cli).await?, "parachain"); cli.verify() } From 7f9b92638d51172d7774af479d55cb4a5a28d0bc Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 17 Feb 2025 22:53:29 +0100 Subject: [PATCH 11/27] refactor: improve docs and code --- crates/pop-cli/src/commands/build/spec.rs | 54 ++++++++++----------- crates/pop-cli/src/commands/call/chain.rs | 20 ++++++-- crates/pop-cli/src/commands/up/parachain.rs | 4 +- 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index b1b84a8e..26962771 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -508,6 +508,33 @@ impl BuildSpec { Ok("spec") } + /// Generates plain and raw chain specification files. + fn generate_chain_spec( + &self, + binary_path: &PathBuf, + spinner: &ProgressBar, + ) -> anyhow::Result { + let BuildSpec { output_file, chain, .. } = self; + spinner.start("Generating chain specification..."); + + // Generate plain chain spec. + generate_plain_chain_spec(&binary_path, output_file, self.default_bootnode, chain)?; + // Customize spec based on input. + self.customize()?; + + // Generate raw spec. + spinner.set_message("Generating raw chain specification..."); + let spec_name = &output_file + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or(DEFAULT_SPEC_NAME) + .trim_end_matches(".json"); + let raw_spec_name = format!("{spec_name}-raw.json"); + let raw_chain_spec = generate_raw_chain_spec(&binary_path, output_file, &raw_spec_name)?; + + Ok(raw_chain_spec) + } + /// Generates chain specification files and returns the file paths for the generated genesis /// code and genesis state files. /// @@ -537,33 +564,6 @@ impl BuildSpec { Ok((genesis_state, genesis_code)) } - /// Generates plain and raw chain specification files. - fn generate_chain_spec( - &self, - binary_path: &PathBuf, - spinner: &ProgressBar, - ) -> anyhow::Result { - let BuildSpec { output_file, chain, .. } = self; - spinner.start("Generating chain specification..."); - - // Generate plain chain spec. - generate_plain_chain_spec(&binary_path, output_file, self.default_bootnode, chain)?; - // Customize spec based on input. - self.customize()?; - - // Generate raw spec. - spinner.set_message("Generating raw chain specification..."); - let spec_name = &output_file - .file_name() - .and_then(|s| s.to_str()) - .unwrap_or(DEFAULT_SPEC_NAME) - .trim_end_matches(".json"); - let raw_spec_name = format!("{spec_name}-raw.json"); - let raw_chain_spec = generate_raw_chain_spec(&binary_path, output_file, &raw_spec_name)?; - - Ok(raw_chain_spec) - } - // Customize a chain specification. fn customize(&self) -> anyhow::Result<()> { let mut chain_spec = ChainSpec::from(&self.output_file)?; diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 2c636fe0..8739d84d 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -110,9 +110,15 @@ impl CallChainCommand { // Sign and submit the extrinsic. let result = if self.use_wallet { let call_data = xt.encode_call_data(&chain.client.metadata())?; - submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, &mut cli) - .await?; - Ok(()) + match submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, &mut cli) + .await + { + Ok(_) => Ok(()), + Err(e) => { + display_message(&e.to_string(), false, &mut cli)?; + break; + }, + } } else { call.submit_extrinsic(&chain.client, &chain.url, xt, &mut cli).await }; @@ -476,7 +482,13 @@ impl Call { } } -// Sign and submit an extrinsic using wallet integration, then returns the resulting events. +/// Sign and submit an extrinsic using wallet integration, then returns the resulting events. +/// +/// # Arguments +/// * `client` - The client used to interact with the chain. +/// * `url` - Endpoint of the node. +/// * `call_data` - The call data to be signed. +/// * `cli` - The CLI implementation to be used. pub(crate) async fn submit_extrinsic_with_wallet( client: &OnlineClient, url: &Url, diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 4c67f743..bc282427 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -27,10 +27,10 @@ pub struct UpParachainCommand { /// Parachain ID to use. If not specified, a new ID will be reserved. #[arg(short, long)] pub(crate) id: Option, - /// Path to the genesis state file. + /// Path to the genesis state file. If not specified, it will be generated. #[arg(short = 'G', long = "genesis-state")] pub(crate) genesis_state: Option, - /// Path to the genesis code file. + /// Path to the genesis code file. If not specified, it will be generated. #[arg(short = 'C', long = "genesis-code")] pub(crate) genesis_code: Option, /// Websocket endpoint of the relay chain. From ece55079353bae24503d15a50361f0e2bea46fcb Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 17 Feb 2025 23:24:26 +0100 Subject: [PATCH 12/27] test: fix change_working_directory_works --- crates/pop-cli/src/commands/up/parachain.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index bc282427..6df39989 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -237,6 +237,7 @@ mod tests { #[tokio::test] async fn change_working_directory_works() -> Result<()> { + let original_dir = std::env::current_dir()?; let temp_dir = tempdir()?; let my_parachain_path = Some(temp_dir.path().to_path_buf()); change_working_directory(&my_parachain_path)?; @@ -244,6 +245,9 @@ mod tests { change_working_directory(&None)?; assert_eq!(fs::canonicalize(env::current_dir()?)?, fs::canonicalize(temp_dir.path())?); + + // Reset working directory back to original + change_working_directory(&Some(original_dir))?; Ok(()) } From e4138717e2f66e86131c3092417b1d77adfcc329 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 18 Feb 2025 09:06:06 +0100 Subject: [PATCH 13/27] fix: clippy warnings --- crates/pop-cli/src/commands/build/spec.rs | 6 +++--- crates/pop-cli/src/commands/call/chain.rs | 2 +- crates/pop-cli/src/commands/up/parachain.rs | 17 ++++++----------- crates/pop-parachains/src/call/mod.rs | 4 ++-- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 26962771..eed7eb39 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -511,14 +511,14 @@ impl BuildSpec { /// Generates plain and raw chain specification files. fn generate_chain_spec( &self, - binary_path: &PathBuf, + binary_path: &Path, spinner: &ProgressBar, ) -> anyhow::Result { let BuildSpec { output_file, chain, .. } = self; spinner.start("Generating chain specification..."); // Generate plain chain spec. - generate_plain_chain_spec(&binary_path, output_file, self.default_bootnode, chain)?; + generate_plain_chain_spec(binary_path, output_file, self.default_bootnode, chain)?; // Customize spec based on input. self.customize()?; @@ -530,7 +530,7 @@ impl BuildSpec { .unwrap_or(DEFAULT_SPEC_NAME) .trim_end_matches(".json"); let raw_spec_name = format!("{spec_name}-raw.json"); - let raw_chain_spec = generate_raw_chain_spec(&binary_path, output_file, &raw_spec_name)?; + let raw_chain_spec = generate_raw_chain_spec(binary_path, output_file, &raw_spec_name)?; Ok(raw_chain_spec) } diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 8739d84d..c6835f73 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -510,7 +510,7 @@ pub(crate) async fn submit_extrinsic_with_wallet( spinner.stop(format!("Extrinsic submitted with hash: {:?}", result.extrinsic_hash())); Ok(result) } else { - return Err(anyhow!("No signed payload received.")); + Err(anyhow!("No signed payload received.")) } } diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 6df39989..f3f06169 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -50,7 +50,7 @@ impl UpParachainCommand { match reserve_para_id(&chain, cli).await { Ok(id) => id, Err(e) => { - cli.outro_cancel(&format!("{}", e))?; + cli.outro_cancel(format!("{}", e))?; return Ok(()); }, } @@ -64,7 +64,7 @@ impl UpParachainCommand { match generate_spec_files(para_id, self.path, cli).await { Ok(files) => files, Err(e) => { - cli.outro_cancel(&format!("Failed to generate spec files: {}", e))?; + cli.outro_cancel(format!("Failed to generate spec files: {}", e))?; return Ok(()); }, } @@ -73,7 +73,7 @@ impl UpParachainCommand { cli.info("Registering a parachain ID")?; if let Err(e) = register_parachain(&chain, para_id, genesis_state, genesis_code, cli).await { - cli.outro_cancel(&format!("Failed to register parachain: {}", e))?; + cli.outro_cancel(format!("Failed to register parachain: {}", e))?; return Ok(()); } @@ -151,14 +151,9 @@ fn change_working_directory(path: &Option) -> Result<()> { /// Configures the chain specification requirements. async fn configure_build_spec(id: u32, cli: &mut impl Cli) -> Result { - Ok(BuildSpecCommand { - id: Some(id), - genesis_code: true, - genesis_state: true, - ..Default::default() - } - .configure_build_spec(cli) - .await?) + BuildSpecCommand { id: Some(id), genesis_code: true, genesis_state: true, ..Default::default() } + .configure_build_spec(cli) + .await } /// Registers a parachain by submitting an extrinsic. diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 95a339d0..7ad7bcc0 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -95,13 +95,13 @@ pub async fn submit_signed_extrinsic( let hex_encoded = from_hex(&payload).map_err(|e| Error::CallDataDecodingError(e.to_string()))?; let extrinsic = SubmittableExtrinsic::from_bytes(client, hex_encoded); - Ok(extrinsic + extrinsic .submit_and_watch() .await .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))? .wait_for_finalized_success() .await - .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?) + .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e))) } /// Encodes the call data for a given extrinsic into a hexadecimal string. From 7d87f17191e66f7d9dbfa689df7749feefff47de Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 20 Feb 2025 09:52:47 +0100 Subject: [PATCH 14/27] refactor: move submit_extrinsic_with_wallet in a common file --- crates/pop-cli/src/commands/call/chain.rs | 39 ++------------------- crates/pop-cli/src/commands/up/parachain.rs | 3 +- crates/pop-cli/src/common/wallet.rs | 29 +++++++++++++++ 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index c6835f73..f418b6a9 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -4,16 +4,15 @@ use std::path::Path; use crate::{ cli::{self, traits::*}, - common::wallet::{prompt_to_use_wallet, request_signature}, + common::wallet::{prompt_to_use_wallet, submit_extrinsic_with_wallet}, }; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ construct_extrinsic, construct_sudo_extrinsic, decode_call_data, encode_call_data, find_dispatchable_by_name, find_pallet_by_name, parse_chain_metadata, set_up_client, - sign_and_submit_extrinsic, submit_signed_extrinsic, supported_actions, Action, CallData, - DynamicPayload, ExtrinsicEvents, Function, OnlineClient, Pallet, Param, Payload, - SubstrateConfig, + sign_and_submit_extrinsic, supported_actions, Action, CallData, DynamicPayload, Function, + OnlineClient, Pallet, Param, Payload, SubstrateConfig, }; use url::Url; @@ -482,38 +481,6 @@ impl Call { } } -/// Sign and submit an extrinsic using wallet integration, then returns the resulting events. -/// -/// # Arguments -/// * `client` - The client used to interact with the chain. -/// * `url` - Endpoint of the node. -/// * `call_data` - The call data to be signed. -/// * `cli` - The CLI implementation to be used. -pub(crate) async fn submit_extrinsic_with_wallet( - client: &OnlineClient, - url: &Url, - call_data: Vec, - cli: &mut impl Cli, -) -> Result> { - let maybe_payload = request_signature(call_data, url.to_string()).await?; - if let Some(payload) = maybe_payload { - cli.success("Signed payload received.")?; - let spinner = cliclack::spinner(); - spinner.start( - "Submitting the extrinsic and then waiting for finalization, please be patient...", - ); - - let result = submit_signed_extrinsic(client.clone(), payload) - .await - .map_err(|err| anyhow!("{}", format!("{err:?}")))?; - - spinner.stop(format!("Extrinsic submitted with hash: {:?}", result.extrinsic_hash())); - Ok(result) - } else { - Err(anyhow!("No signed payload received.")) - } -} - // Displays a message to the user, with formatting based on the success status. fn display_message(message: &str, success: bool, cli: &mut impl Cli) -> Result<()> { if success { diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index f3f06169..5d45ff17 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -2,8 +2,9 @@ use crate::{ build::spec::{BuildSpec, BuildSpecCommand}, - call::chain::{submit_extrinsic_with_wallet, Chain}, + call::chain::Chain, cli::traits::*, + common::wallet::submit_extrinsic_with_wallet, }; use anyhow::{anyhow, Result}; use clap::Args; diff --git a/crates/pop-cli/src/common/wallet.rs b/crates/pop-cli/src/common/wallet.rs index 1aedd029..5b8f5a83 100644 --- a/crates/pop-cli/src/common/wallet.rs +++ b/crates/pop-cli/src/common/wallet.rs @@ -4,7 +4,10 @@ use crate::{ cli::traits::Cli, wallet_integration::{FrontendFromString, TransactionData, WalletIntegrationManager}, }; +use anyhow::{anyhow, Result}; use cliclack::{log, spinner}; +use pop_parachains::{submit_signed_extrinsic, ExtrinsicEvents, OnlineClient, SubstrateConfig}; +use url::Url; /// The prompt to ask the user if they want to use the wallet for signing. pub const USE_WALLET_PROMPT: &str = "Do you want to use your browser wallet to sign the extrinsic? (Selecting 'No' will prompt you to manually enter the secret key URI for signing, e.g., '//Alice')"; @@ -65,3 +68,29 @@ pub fn prompt_to_use_wallet(cli: &mut impl Cli) -> anyhow::Result { Ok(false) } } + +// Sign and submit an extrinsic using wallet integration, then returns the resulting events. +pub(crate) async fn submit_extrinsic_with_wallet( + client: &OnlineClient, + url: &Url, + call_data: Vec, + cli: &mut impl Cli, +) -> Result> { + let maybe_payload = request_signature(call_data, url.to_string()).await?; + if let Some(payload) = maybe_payload { + cli.success("Signed payload received.")?; + let spinner = cliclack::spinner(); + spinner.start( + "Submitting the extrinsic and then waiting for finalization, please be patient...", + ); + + let result = submit_signed_extrinsic(client.clone(), payload) + .await + .map_err(|err| anyhow!("{}", format!("{err:?}")))?; + + spinner.stop(format!("Extrinsic submitted with hash: {:?}", result.extrinsic_hash())); + Ok(result) + } else { + Err(anyhow!("No signed payload received.")) + } +} From ae3d9a4af3fc48dc3168c2d216caf87c9f9a3ff6 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 20 Feb 2025 11:57:21 +0100 Subject: [PATCH 15/27] refactor: remove unnecesary code --- crates/pop-cli/src/commands/build/spec.rs | 5 ++--- crates/pop-cli/src/commands/call/chain.rs | 10 ++-------- crates/pop-cli/src/commands/up/mod.rs | 3 +-- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index eed7eb39..fda8379c 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -530,9 +530,8 @@ impl BuildSpec { .unwrap_or(DEFAULT_SPEC_NAME) .trim_end_matches(".json"); let raw_spec_name = format!("{spec_name}-raw.json"); - let raw_chain_spec = generate_raw_chain_spec(binary_path, output_file, &raw_spec_name)?; - - Ok(raw_chain_spec) + generate_raw_chain_spec(binary_path, output_file, &raw_spec_name) + .map_err(anyhow::Error::from) } /// Generates chain specification files and returns the file paths for the generated genesis diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index f418b6a9..fac36c32 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -109,15 +109,9 @@ impl CallChainCommand { // Sign and submit the extrinsic. let result = if self.use_wallet { let call_data = xt.encode_call_data(&chain.client.metadata())?; - match submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, &mut cli) + submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, &mut cli) .await - { - Ok(_) => Ok(()), - Err(e) => { - display_message(&e.to_string(), false, &mut cli)?; - break; - }, - } + .map(|_| ()) // Mapping to `()` since we don't need events returned } else { call.submit_extrinsic(&chain.client, &chain.url, xt, &mut cli).await }; diff --git a/crates/pop-cli/src/commands/up/mod.rs b/crates/pop-cli/src/commands/up/mod.rs index 302b181b..5fad429e 100644 --- a/crates/pop-cli/src/commands/up/mod.rs +++ b/crates/pop-cli/src/commands/up/mod.rs @@ -147,8 +147,7 @@ mod tests { async fn detects_parachain_correctly() -> anyhow::Result<()> { let temp_dir = tempfile::tempdir()?; let name = "parachain"; - let path = temp_dir.path(); - let project_path = path.join(name); + let project_path = temp_dir.path().join(name); let config = Config { symbol: "DOT".to_string(), decimals: 18, From 54a676e2e7c20e94841cb2c90dbfaa37fea3439f Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 20 Feb 2025 14:55:45 +0100 Subject: [PATCH 16/27] refactor: UpChainCommand structure --- crates/pop-cli/src/commands/up/mod.rs | 6 +- crates/pop-cli/src/commands/up/parachain.rs | 167 ++++++++++---------- 2 files changed, 90 insertions(+), 83 deletions(-) diff --git a/crates/pop-cli/src/commands/up/mod.rs b/crates/pop-cli/src/commands/up/mod.rs index 5fad429e..b9bc4a5b 100644 --- a/crates/pop-cli/src/commands/up/mod.rs +++ b/crates/pop-cli/src/commands/up/mod.rs @@ -29,7 +29,7 @@ pub(crate) struct UpArgs { #[command(flatten)] #[cfg(feature = "parachain")] - pub(crate) parachain: parachain::UpParachainCommand, + pub(crate) parachain: parachain::UpChainCommand, #[command(flatten)] #[cfg(feature = "contract")] @@ -93,7 +93,7 @@ impl Command { #[cfg(test)] mod tests { - use super::{contract::UpContractCommand, parachain::UpParachainCommand, *}; + use super::{contract::UpContractCommand, parachain::UpChainCommand, *}; use cli::MockCli; use duct::cmd; @@ -122,7 +122,7 @@ mod tests { skip_confirm: false, valid: false, }, - parachain: UpParachainCommand::default(), + parachain: UpChainCommand::default(), command: None, }) } diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 5d45ff17..83f02865 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -12,16 +12,15 @@ use pop_parachains::{ construct_extrinsic, extract_para_id_from_event, find_dispatchable_by_name, parse_chain_metadata, set_up_client, Action, Payload, }; - use std::path::PathBuf; use url::Url; const DEFAULT_URL: &str = "wss://paseo.rpc.amforc.com/"; -const HELP_HEADER: &str = "Parachain deployment options"; +const HELP_HEADER: &str = "Chain deployment options"; #[derive(Args, Clone, Default)] #[clap(next_help_heading = HELP_HEADER)] -pub struct UpParachainCommand { +pub struct UpChainCommand { /// Path to the chain directory. #[clap(skip)] pub(crate) path: Option, @@ -39,49 +38,32 @@ pub struct UpParachainCommand { pub(crate) relay_url: Option, } -impl UpParachainCommand { +impl UpChainCommand { /// Executes the command. pub(crate) async fn execute(self, cli: &mut impl Cli) -> Result<()> { - cli.intro("Deploy a parachain")?; - let chain = self.configure_chain(cli).await?; - let para_id = match self.id { - Some(id) => id, - None => { - cli.info("Reserving a parachain ID...")?; - match reserve_para_id(&chain, cli).await { - Ok(id) => id, - Err(e) => { - cli.outro_cancel(format!("{}", e))?; - return Ok(()); - }, - } + cli.intro("Deploy a chain")?; + let chain_config = match self.prepare_chain_for_registration(cli).await { + Ok(chain) => chain, + Err(e) => { + cli.outro_cancel(format!("{}", e))?; + return Ok(()); }, }; - let (genesis_state, genesis_code) = - match (self.genesis_state.clone(), self.genesis_code.clone()) { - (Some(state), Some(code)) => (state, code), - _ => { - cli.info("Generating the chain spec for your parachain.")?; - match generate_spec_files(para_id, self.path, cli).await { - Ok(files) => files, - Err(e) => { - cli.outro_cancel(format!("Failed to generate spec files: {}", e))?; - return Ok(()); - }, - } - }, - }; - cli.info("Registering a parachain ID")?; - if let Err(e) = register_parachain(&chain, para_id, genesis_state, genesis_code, cli).await - { - cli.outro_cancel(format!("Failed to register parachain: {}", e))?; - return Ok(()); + match chain_config.register_parachain(cli).await { + Ok(_) => cli.success("Chain deployed successfully")?, + Err(e) => cli.outro_cancel(format!("{}", e))?, } - - cli.outro("Parachain deployment complete.")?; Ok(()) } + // Prepares the chain for registration by setting up its configuration. + async fn prepare_chain_for_registration(self, cli: &mut impl Cli) -> Result { + let chain = self.configure_chain(cli).await?; + let para_id = self.resolve_parachain_id(&chain, cli).await?; + let (genesis_state, genesis_code) = self.resolve_genesis_files(para_id, cli).await?; + Ok(UpChain { id: para_id, genesis_state, genesis_code, chain }) + } + // Configures the chain by resolving the URL and fetching its metadata. async fn configure_chain(&self, cli: &mut impl Cli) -> Result { // Resolve url. @@ -104,6 +86,63 @@ impl UpParachainCommand { })?; Ok(Chain { url, client, pallets }) } + // Resolves the parachain ID, reserving a new one if necessary. + async fn resolve_parachain_id(&self, chain: &Chain, cli: &mut impl Cli) -> Result { + match self.id { + Some(id) => Ok(id), + None => { + cli.info("Reserving a parachain ID...")?; + return reserve_para_id(&chain, cli).await; + }, + } + } + // Resolves the genesis state and code files, generating them if necessary. + async fn resolve_genesis_files( + &self, + para_id: u32, + cli: &mut impl Cli, + ) -> Result<(PathBuf, PathBuf)> { + match (self.genesis_state.clone(), self.genesis_code.clone()) { + (Some(state), Some(code)) => Ok((state, code)), + _ => { + cli.info("Generating the chain spec for your parachain.")?; + return generate_spec_files(para_id, self.path.clone(), cli).await; + }, + } + } +} + +// Represents the configuration for deploying a chain. +pub(crate) struct UpChain { + id: u32, + genesis_state: PathBuf, + genesis_code: PathBuf, + chain: Chain, +} +impl UpChain { + // Registers a parachain by submitting an extrinsic. + async fn register_parachain(&self, cli: &mut impl Cli) -> Result<()> { + cli.info("Registering a parachain ID")?; + let call_data = self.prepare_register_parachain_extrinsic()?; + submit_extrinsic_with_wallet(&self.chain.client, &self.chain.url, call_data, cli).await?; + Ok(()) + } + + // Constructs an extrinsic for registering a parachain. + fn prepare_register_parachain_extrinsic(&self) -> Result> { + let UpChain { id, genesis_code, genesis_state, chain } = self; + let ex = find_dispatchable_by_name( + &chain.pallets, + Action::Register.pallet_name(), + Action::Register.function_name(), + )?; + let state = std::fs::read_to_string(genesis_state) + .map_err(|err| anyhow!("Failed to read genesis state file: {}", err.to_string()))?; + let code = std::fs::read_to_string(genesis_code) + .map_err(|err| anyhow!("Failed to read genesis state file: {}", err.to_string()))?; + let xt = construct_extrinsic(ex, vec![id.to_string(), state, code])?; + Ok(xt.encode_call_data(&chain.client.metadata())?) + } } /// Reserves a parachain ID by submitting an extrinsic. @@ -157,39 +196,6 @@ async fn configure_build_spec(id: u32, cli: &mut impl Cli) -> Result .await } -/// Registers a parachain by submitting an extrinsic. -async fn register_parachain( - chain: &Chain, - id: u32, - genesis_state: PathBuf, - genesis_code: PathBuf, - cli: &mut impl Cli, -) -> Result<()> { - let call_data = prepare_register_parachain_extrinsic(chain, id, genesis_state, genesis_code)?; - submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli).await?; - Ok(()) -} - -/// Constructs an extrinsic for registering a parachain. -fn prepare_register_parachain_extrinsic( - chain: &Chain, - id: u32, - genesis_state: PathBuf, - genesis_code: PathBuf, -) -> Result> { - let ex = find_dispatchable_by_name( - &chain.pallets, - Action::Register.pallet_name(), - Action::Register.function_name(), - )?; - let state = std::fs::read_to_string(genesis_state) - .map_err(|err| anyhow!("Failed to read genesis state file: {}", err.to_string()))?; - let code = std::fs::read_to_string(genesis_code) - .map_err(|err| anyhow!("Failed to read genesis state file: {}", err.to_string()))?; - let xt = construct_extrinsic(ex, vec![id.to_string(), state, code])?; - Ok(xt.encode_call_data(&chain.client.metadata())?) -} - #[cfg(test)] mod tests { use super::*; @@ -212,7 +218,7 @@ mod tests { "Enter the relay chain node URL to deploy your parachain", POLKADOT_NETWORK_URL.into(), ); - let chain = UpParachainCommand::default().configure_chain(&mut cli).await?; + let chain = UpChainCommand::default().configure_chain(&mut cli).await?; assert_eq!(chain.url, Url::parse(POLKADOT_NETWORK_URL)?); cli.verify() } @@ -220,7 +226,7 @@ mod tests { #[tokio::test] async fn prepare_reserve_para_id_extrinsic_works() -> Result<()> { let mut cli = MockCli::new(); - let chain = UpParachainCommand { + let chain = UpChainCommand { relay_url: Some(Url::parse(POLKADOT_NETWORK_URL)?), ..Default::default() } @@ -250,7 +256,7 @@ mod tests { #[tokio::test] async fn prepare_register_parachain_extrinsic_works() -> Result<()> { let mut cli = MockCli::new(); - let chain = UpParachainCommand { + let chain = UpChainCommand { relay_url: Some(Url::parse(POLKADOT_NETWORK_URL)?), ..Default::default() } @@ -263,12 +269,13 @@ mod tests { std::fs::write(&genesis_state_path, "0x1234")?; std::fs::write(&genesis_code_path, "0x1234")?; - let call_data = prepare_register_parachain_extrinsic( - &chain, - 2000, - genesis_state_path, - genesis_code_path, - )?; + let call_data = UpChain { + id: 2000, + genesis_state: genesis_state_path, + genesis_code: genesis_code_path, + chain, + } + .prepare_register_parachain_extrinsic()?; assert_eq!(call_data, decode_call_data("0x4600d0070000081234081234")?); Ok(()) } From db6e30da21fd763d81174e6888c6b07767838426 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 20 Feb 2025 15:16:29 +0100 Subject: [PATCH 17/27] test: adjust tests to refactored struct --- crates/pop-cli/src/commands/up/parachain.rs | 157 +++++++------------- 1 file changed, 54 insertions(+), 103 deletions(-) diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 83f02865..3ede2e41 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 use crate::{ - build::spec::{BuildSpec, BuildSpecCommand}, + build::spec::BuildSpecCommand, call::chain::Chain, cli::traits::*, common::wallet::submit_extrinsic_with_wallet, @@ -175,51 +175,54 @@ async fn generate_spec_files( path: Option, cli: &mut impl Cli, ) -> anyhow::Result<(PathBuf, PathBuf)> { - change_working_directory(&path)?; - let build_spec = configure_build_spec(id, cli).await?; - build_spec.generate_genesis_artifacts(cli) -} - -/// Changes the working directory if a path is provided, ensuring the build spec process runs in the -/// correct context. -fn change_working_directory(path: &Option) -> Result<()> { + // Changes the working directory if a path is provided to ensure the build spec process runs in + // the correct context. if let Some(path) = path { std::env::set_current_dir(path)?; } - Ok(()) -} - -/// Configures the chain specification requirements. -async fn configure_build_spec(id: u32, cli: &mut impl Cli) -> Result { - BuildSpecCommand { id: Some(id), genesis_code: true, genesis_state: true, ..Default::default() } - .configure_build_spec(cli) - .await + let build_spec = BuildSpecCommand { + id: Some(id), + genesis_code: true, + genesis_state: true, + ..Default::default() + } + .configure_build_spec(cli) + .await?; + build_spec.generate_genesis_artifacts(cli) } #[cfg(test)] mod tests { use super::*; - use crate::{ - build::spec::{ChainType, RelayChain}, - cli::MockCli, - }; - use pop_common::Profile; + use crate::cli::MockCli; use pop_parachains::decode_call_data; - use std::{env, fs}; - use strum::{EnumMessage, VariantArray}; + use std::fs; use tempfile::tempdir; use url::Url; const POLKADOT_NETWORK_URL: &str = "wss://polkadot-rpc.publicnode.com"; + const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; #[tokio::test] - async fn configure_chain_works() -> Result<()> { + async fn prepare_chain_for_registration_works() -> Result<()> { let mut cli = MockCli::new().expect_input( "Enter the relay chain node URL to deploy your parachain", POLKADOT_NETWORK_URL.into(), ); - let chain = UpChainCommand::default().configure_chain(&mut cli).await?; - assert_eq!(chain.url, Url::parse(POLKADOT_NETWORK_URL)?); + let (genesis_state, genesis_code) = create_temp_genesis_files()?; + let chain_config = UpChainCommand { + id: Some(2000), + genesis_state: Some(genesis_state.clone()), + genesis_code: Some(genesis_code.clone()), + ..Default::default() + } + .prepare_chain_for_registration(&mut cli) + .await?; + + assert_eq!(chain_config.id, 2000); + assert_eq!(chain_config.genesis_code, genesis_code); + assert_eq!(chain_config.genesis_state, genesis_state); + assert_eq!(chain_config.chain.url, Url::parse(POLKADOT_NETWORK_URL)?); cli.verify() } @@ -238,19 +241,23 @@ mod tests { } #[tokio::test] - async fn change_working_directory_works() -> Result<()> { - let original_dir = std::env::current_dir()?; - let temp_dir = tempdir()?; - let my_parachain_path = Some(temp_dir.path().to_path_buf()); - change_working_directory(&my_parachain_path)?; - assert_eq!(fs::canonicalize(env::current_dir()?)?, fs::canonicalize(temp_dir.path())?); - - change_working_directory(&None)?; - assert_eq!(fs::canonicalize(env::current_dir()?)?, fs::canonicalize(temp_dir.path())?); + async fn register_parachain_fails_wrong_chain() -> Result<()> { + let mut cli = MockCli::new() + .expect_intro("Deploy a chain") + .expect_info("Registering a parachain ID") + .expect_outro_cancel("Failed to find the pallet Registrar"); + let (genesis_state, genesis_code) = create_temp_genesis_files()?; + UpChainCommand { + id: Some(2000), + genesis_state: Some(genesis_state.clone()), + genesis_code: Some(genesis_code.clone()), + relay_url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), + path: None, + } + .execute(&mut cli) + .await?; - // Reset working directory back to original - change_working_directory(&Some(original_dir))?; - Ok(()) + cli.verify() } #[tokio::test] @@ -280,71 +287,15 @@ mod tests { Ok(()) } - #[tokio::test] - async fn configure_build_spec_works() -> Result<()> { - let mut cli = MockCli::new().expect_input("Provide the chain specification to use (e.g. dev, local, custom or a path to an existing file)", "dev".to_string()) - .expect_input( - "Name or path for the plain chain spec file:", "output_file".to_string()) - .expect_input( - "Enter the protocol ID that will identify your network:", "protocol_id".to_string()) - .expect_select( - "Choose the chain type: ", - Some(false), - true, - Some(chain_types()), - ChainType::Development as usize, - ).expect_select( - "Choose the relay your chain will be connecting to: ", - Some(false), - true, - Some(relays()), - RelayChain::PaseoLocal as usize, - ).expect_select( - "Choose the build profile of the binary that should be used: ", - Some(false), - true, - Some(profiles()), - Profile::Release as usize, - ); - - configure_build_spec(2000, &mut cli).await?; - cli.verify()?; - Ok(()) - } - - fn relays() -> Vec<(String, String)> { - RelayChain::VARIANTS - .iter() - .map(|variant| { - ( - variant.get_message().unwrap_or(variant.as_ref()).into(), - variant.get_detailed_message().unwrap_or_default().into(), - ) - }) - .collect() - } + // Creates temporary files to act as `genesis_state` and `genesis_code` files. + fn create_temp_genesis_files() -> Result<(PathBuf, PathBuf)> { + let temp_dir = tempdir()?; // Create a temporary directory + let genesis_state_path = temp_dir.path().join("genesis_state"); + let genesis_code_path = temp_dir.path().join("genesis_code.wasm"); - fn chain_types() -> Vec<(String, String)> { - ChainType::VARIANTS - .iter() - .map(|variant| { - ( - variant.get_message().unwrap_or(variant.as_ref()).into(), - variant.get_detailed_message().unwrap_or_default().into(), - ) - }) - .collect() - } + fs::write(&genesis_state_path, "0x1234")?; + fs::write(&genesis_code_path, "0x1234")?; - fn profiles() -> Vec<(String, String)> { - Profile::VARIANTS - .iter() - .map(|variant| { - ( - variant.get_message().unwrap_or(variant.as_ref()).into(), - variant.get_detailed_message().unwrap_or_default().into(), - ) - }) - .collect() + Ok((genesis_state_path, genesis_code_path)) } } From 50983c12dd6bca590998240cf4d92766655f098e Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 20 Feb 2025 15:33:32 +0100 Subject: [PATCH 18/27] refactor: renaming prepare_register_parachain_call_data and prepare_rerve_parachain_call_data --- crates/pop-cli/src/commands/build/spec.rs | 2 +- crates/pop-cli/src/commands/call/chain.rs | 6 ++--- crates/pop-cli/src/commands/up/parachain.rs | 30 ++++++++++----------- crates/pop-parachains/src/errors.rs | 2 +- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index fda8379c..c78a5c30 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -508,7 +508,7 @@ impl BuildSpec { Ok("spec") } - /// Generates plain and raw chain specification files. + /// Generates plain and raw chain specification files, and returns the path to the latter. fn generate_chain_spec( &self, binary_path: &Path, diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index fac36c32..c90ea66f 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -371,8 +371,8 @@ pub(crate) struct Chain { /// Represents a configured dispatchable function call, including the pallet, function, arguments, /// and signing options. -#[derive(Clone)] -struct Call { +#[derive(Clone, Default)] +pub(crate) struct Call { /// The dispatchable function to execute. function: Function, /// The dispatchable function arguments, encoded as strings. @@ -393,7 +393,7 @@ struct Call { impl Call { // Prepares the extrinsic. - fn prepare_extrinsic( + pub(crate) fn prepare_extrinsic( &self, client: &OnlineClient, cli: &mut impl Cli, diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 3ede2e41..5904f474 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -1,9 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 use crate::{ - build::spec::BuildSpecCommand, - call::chain::Chain, - cli::traits::*, + build::spec::BuildSpecCommand, call::chain::Chain, cli::traits::*, common::wallet::submit_extrinsic_with_wallet, }; use anyhow::{anyhow, Result}; @@ -123,13 +121,13 @@ impl UpChain { // Registers a parachain by submitting an extrinsic. async fn register_parachain(&self, cli: &mut impl Cli) -> Result<()> { cli.info("Registering a parachain ID")?; - let call_data = self.prepare_register_parachain_extrinsic()?; + let call_data = self.prepare_register_parachain_call_data()?; submit_extrinsic_with_wallet(&self.chain.client, &self.chain.url, call_data, cli).await?; Ok(()) } - // Constructs an extrinsic for registering a parachain. - fn prepare_register_parachain_extrinsic(&self) -> Result> { + // Prepares and returns the encoded call data for registering a parachain. + fn prepare_register_parachain_call_data(&self) -> Result> { let UpChain { id, genesis_code, genesis_state, chain } = self; let ex = find_dispatchable_by_name( &chain.pallets, @@ -145,10 +143,10 @@ impl UpChain { } } -/// Reserves a parachain ID by submitting an extrinsic. +// Reserves a parachain ID by submitting an extrinsic. async fn reserve_para_id(chain: &Chain, cli: &mut impl Cli) -> Result { - let call_data = prepare_reserve_para_id_extrinsic(chain)?; - let events = submit_extrinsic_with_wallet(&chain.client.clone(), &chain.url, call_data, cli) + let call_data = prepare_reserve_parachain_call_data(chain)?; + let events = submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli) .await .map_err(|e| anyhow::anyhow!("Parachain ID reservation failed: {}", e))?; let para_id = extract_para_id_from_event(&events).await.map_err(|_| { @@ -158,8 +156,8 @@ async fn reserve_para_id(chain: &Chain, cli: &mut impl Cli) -> Result { Ok(para_id) } -/// Constructs an extrinsic for reserving a parachain ID. -fn prepare_reserve_para_id_extrinsic(chain: &Chain) -> Result> { +// Prepares and returns the encoded call data for reserving a parachain ID. +fn prepare_reserve_parachain_call_data(chain: &Chain) -> Result> { let function = find_dispatchable_by_name( &chain.pallets, Action::Reserve.pallet_name(), @@ -169,7 +167,7 @@ fn prepare_reserve_para_id_extrinsic(chain: &Chain) -> Result> { Ok(xt.encode_call_data(&chain.client.metadata())?) } -/// Generates chain spec files for the parachain. +// Generates chain spec files for the parachain. async fn generate_spec_files( id: u32, path: Option, @@ -227,7 +225,7 @@ mod tests { } #[tokio::test] - async fn prepare_reserve_para_id_extrinsic_works() -> Result<()> { + async fn prepare_reserve_parachain_call_data_works() -> Result<()> { let mut cli = MockCli::new(); let chain = UpChainCommand { relay_url: Some(Url::parse(POLKADOT_NETWORK_URL)?), @@ -235,7 +233,7 @@ mod tests { } .configure_chain(&mut cli) .await?; - let call_data = prepare_reserve_para_id_extrinsic(&chain)?; + let call_data = prepare_reserve_parachain_call_data(&chain)?; assert_eq!(call_data, decode_call_data("0x4605")?); Ok(()) } @@ -261,7 +259,7 @@ mod tests { } #[tokio::test] - async fn prepare_register_parachain_extrinsic_works() -> Result<()> { + async fn prepare_register_parachain_call_data_works() -> Result<()> { let mut cli = MockCli::new(); let chain = UpChainCommand { relay_url: Some(Url::parse(POLKADOT_NETWORK_URL)?), @@ -282,7 +280,7 @@ mod tests { genesis_code: genesis_code_path, chain, } - .prepare_register_parachain_extrinsic()?; + .prepare_register_parachain_call_data()?; assert_eq!(call_data, decode_call_data("0x4600d0070000081234081234")?); Ok(()) } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 0288c441..889bd2be 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -27,7 +27,7 @@ pub enum Error { CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, - /// The specified event was not found in the extrinsic events. + /// The specified event was not found. #[error("Event {0} not found.")] EventNotFound(String), /// An error occurred during the submission of an extrinsic. From cfc566e5e380849d566090db5db3e2ac887b1c63 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 20 Feb 2025 15:41:30 +0100 Subject: [PATCH 19/27] refactor: move events module --- crates/pop-cli/src/commands/up/parachain.rs | 2 +- crates/pop-parachains/src/call/metadata/mod.rs | 1 - crates/pop-parachains/src/lib.rs | 2 +- crates/pop-parachains/src/{call/metadata => utils}/events.rs | 4 +--- crates/pop-parachains/src/utils/mod.rs | 1 + crates/pop-parachains/src/utils/onboard.rs | 0 6 files changed, 4 insertions(+), 6 deletions(-) rename crates/pop-parachains/src/{call/metadata => utils}/events.rs (88%) delete mode 100644 crates/pop-parachains/src/utils/onboard.rs diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 5904f474..3f97df65 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -149,7 +149,7 @@ async fn reserve_para_id(chain: &Chain, cli: &mut impl Cli) -> Result { let events = submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, cli) .await .map_err(|e| anyhow::anyhow!("Parachain ID reservation failed: {}", e))?; - let para_id = extract_para_id_from_event(&events).await.map_err(|_| { + let para_id = extract_para_id_from_event(&events).map_err(|_| { anyhow::anyhow!("Unable to parse the event. Specify the parachain ID manually with --id.") })?; cli.success(format!("Successfully reserved parachain ID: {}", para_id))?; diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 38f0bfaa..5a9a9a2c 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -7,7 +7,6 @@ use std::fmt::{Display, Formatter}; use subxt::{dynamic::Value, utils::to_hex, Metadata, OnlineClient, SubstrateConfig}; pub mod action; -pub mod events; pub mod params; /// Represents a pallet in the blockchain, including its dispatchable functions. diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 9e71dcbf..4fe7003b 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -21,7 +21,6 @@ pub use call::{ construct_extrinsic, construct_sudo_extrinsic, decode_call_data, encode_call_data, metadata::{ action::{supported_actions, Action}, - events::extract_para_id_from_event, find_dispatchable_by_name, find_pallet_by_name, params::Param, parse_chain_metadata, Function, Pallet, @@ -33,6 +32,7 @@ pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; pub use relay::{clear_dmpq, RelayChain}; +pub use utils::events::extract_para_id_from_event; // External export from subxt. pub use subxt::{ blocks::ExtrinsicEvents, diff --git a/crates/pop-parachains/src/call/metadata/events.rs b/crates/pop-parachains/src/utils/events.rs similarity index 88% rename from crates/pop-parachains/src/call/metadata/events.rs rename to crates/pop-parachains/src/utils/events.rs index f15957bc..2f47e7e3 100644 --- a/crates/pop-parachains/src/call/metadata/events.rs +++ b/crates/pop-parachains/src/utils/events.rs @@ -25,9 +25,7 @@ impl StaticEvent for Reserved { /// /// # Arguments /// * `events` - The extrinsic events from a transaction. -pub async fn extract_para_id_from_event( - events: &ExtrinsicEvents, -) -> Result { +pub fn extract_para_id_from_event(events: &ExtrinsicEvents) -> Result { let reserved_event = events.find_first::()?; reserved_event .map(|event| event.para_id) diff --git a/crates/pop-parachains/src/utils/mod.rs b/crates/pop-parachains/src/utils/mod.rs index 265ebafd..a59eacd9 100644 --- a/crates/pop-parachains/src/utils/mod.rs +++ b/crates/pop-parachains/src/utils/mod.rs @@ -1,3 +1,4 @@ // SPDX-License-Identifier: GPL-3.0 +pub mod events; pub mod helpers; diff --git a/crates/pop-parachains/src/utils/onboard.rs b/crates/pop-parachains/src/utils/onboard.rs deleted file mode 100644 index e69de29b..00000000 From 93053df30135191304d38f214b8891da71ac0596 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 20 Feb 2025 16:26:22 +0100 Subject: [PATCH 20/27] fix: submit_extrinsic_with_wallet under parachain feature --- crates/pop-cli/src/common/wallet.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/pop-cli/src/common/wallet.rs b/crates/pop-cli/src/common/wallet.rs index 5b8f5a83..a648285d 100644 --- a/crates/pop-cli/src/common/wallet.rs +++ b/crates/pop-cli/src/common/wallet.rs @@ -6,6 +6,7 @@ use crate::{ }; use anyhow::{anyhow, Result}; use cliclack::{log, spinner}; +#[cfg(feature = "parachain")] use pop_parachains::{submit_signed_extrinsic, ExtrinsicEvents, OnlineClient, SubstrateConfig}; use url::Url; @@ -70,6 +71,7 @@ pub fn prompt_to_use_wallet(cli: &mut impl Cli) -> anyhow::Result { } // Sign and submit an extrinsic using wallet integration, then returns the resulting events. +#[cfg(feature = "parachain")] pub(crate) async fn submit_extrinsic_with_wallet( client: &OnlineClient, url: &Url, From 849dff007ba72e44158b91c7b7c879af7532b463 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 20 Feb 2025 17:07:55 +0100 Subject: [PATCH 21/27] refactor: remove unnecesary code --- crates/pop-cli/src/commands/call/chain.rs | 4 ++-- crates/pop-cli/src/commands/up/parachain.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index c90ea66f..5a3aacba 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -372,7 +372,7 @@ pub(crate) struct Chain { /// Represents a configured dispatchable function call, including the pallet, function, arguments, /// and signing options. #[derive(Clone, Default)] -pub(crate) struct Call { +pub struct Call { /// The dispatchable function to execute. function: Function, /// The dispatchable function arguments, encoded as strings. @@ -393,7 +393,7 @@ pub(crate) struct Call { impl Call { // Prepares the extrinsic. - pub(crate) fn prepare_extrinsic( + pub fn prepare_extrinsic( &self, client: &OnlineClient, cli: &mut impl Cli, diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 3f97df65..ae329d56 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -90,7 +90,7 @@ impl UpChainCommand { Some(id) => Ok(id), None => { cli.info("Reserving a parachain ID...")?; - return reserve_para_id(&chain, cli).await; + reserve_para_id(chain, cli).await }, } } @@ -104,7 +104,7 @@ impl UpChainCommand { (Some(state), Some(code)) => Ok((state, code)), _ => { cli.info("Generating the chain spec for your parachain.")?; - return generate_spec_files(para_id, self.path.clone(), cli).await; + generate_spec_files(para_id, self.path.clone(), cli).await }, } } From 74cf39056b7c20d048da607f9366cee0b21958f9 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 20 Feb 2025 17:40:09 +0100 Subject: [PATCH 22/27] test: increase coverage with reserve_parachain_id_fails_wrong_chain and resolve_genesis_files_fails_wrong_path --- crates/pop-cli/src/commands/up/parachain.rs | 124 +++++++++++++++++++- 1 file changed, 120 insertions(+), 4 deletions(-) diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index ae329d56..d06e83d5 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -89,7 +89,7 @@ impl UpChainCommand { match self.id { Some(id) => Ok(id), None => { - cli.info("Reserving a parachain ID...")?; + cli.info("Reserving a parachain ID")?; reserve_para_id(chain, cli).await }, } @@ -103,7 +103,7 @@ impl UpChainCommand { match (self.genesis_state.clone(), self.genesis_code.clone()) { (Some(state), Some(code)) => Ok((state, code)), _ => { - cli.info("Generating the chain spec for your parachain.")?; + cli.info("Generating the chain spec for your parachain")?; generate_spec_files(para_id, self.path.clone(), cli).await }, } @@ -192,9 +192,15 @@ async fn generate_spec_files( #[cfg(test)] mod tests { use super::*; - use crate::cli::MockCli; + use crate::{ + build::spec::{ChainType, RelayChain}, + cli::MockCli, + }; + use duct::cmd; + use pop_common::Profile; use pop_parachains::decode_call_data; - use std::fs; + use std::{env, fs}; + use strum::{EnumMessage, VariantArray}; use tempfile::tempdir; use url::Url; @@ -238,6 +244,80 @@ mod tests { Ok(()) } + #[tokio::test] + async fn reserve_parachain_id_fails_wrong_chain() -> Result<()> { + let mut cli = MockCli::new() + .expect_intro("Deploy a chain") + .expect_info("Reserving a parachain ID") + .expect_outro_cancel("Failed to find the pallet Registrar"); + let (genesis_state, genesis_code) = create_temp_genesis_files()?; + UpChainCommand { + id: None, + genesis_state: Some(genesis_state.clone()), + genesis_code: Some(genesis_code.clone()), + relay_url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), + path: None, + } + .execute(&mut cli) + .await?; + + cli.verify() + } + + #[tokio::test] + async fn resolve_genesis_files_fails_wrong_path() -> Result<()> { + // Mock a project path without node. + let name = "hello_world"; + let temp_dir = tempfile::tempdir()?; + let path = temp_dir.path(); + let project_path = path.join(name); + cmd("cargo", ["new", name, "--bin"]).dir(&path).run()?; + let original_dir = std::env::current_dir()?; + + let mut cli = MockCli::new() + .expect_intro("Deploy a chain") + .expect_info("Generating the chain spec for your parachain") + .expect_input("Provide the chain specification to use (e.g. dev, local, custom or a path to an existing file)", "dev".to_string()) + .expect_input( + "Name or path for the plain chain spec file:", "output_file".to_string()) + .expect_input( + "Enter the protocol ID that will identify your network:", "protocol_id".to_string()) + .expect_select( + "Choose the chain type: ", + Some(false), + true, + Some(chain_types()), + ChainType::Development as usize, + ).expect_select( + "Choose the relay your chain will be connecting to: ", + Some(false), + true, + Some(relays()), + RelayChain::PaseoLocal as usize, + ).expect_select( + "Choose the build profile of the binary that should be used: ", + Some(false), + true, + Some(profiles()), + Profile::Release as usize, + ).expect_outro_cancel(format!("Failed to get manifest path: {}/node/Cargo.toml", fs::canonicalize(&project_path)?.display().to_string())); + + UpChainCommand { + id: Some(2000), + genesis_state: None, + genesis_code: None, + relay_url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), + path: Some(project_path.clone()), + } + .execute(&mut cli) + .await?; + + assert_eq!(fs::canonicalize(env::current_dir()?)?, fs::canonicalize(project_path)?); + // Reset working directory back to original + std::env::set_current_dir(original_dir)?; + cli.verify() + } + #[tokio::test] async fn register_parachain_fails_wrong_chain() -> Result<()> { let mut cli = MockCli::new() @@ -296,4 +376,40 @@ mod tests { Ok((genesis_state_path, genesis_code_path)) } + + fn relays() -> Vec<(String, String)> { + RelayChain::VARIANTS + .iter() + .map(|variant| { + ( + variant.get_message().unwrap_or(variant.as_ref()).into(), + variant.get_detailed_message().unwrap_or_default().into(), + ) + }) + .collect() + } + + fn chain_types() -> Vec<(String, String)> { + ChainType::VARIANTS + .iter() + .map(|variant| { + ( + variant.get_message().unwrap_or(variant.as_ref()).into(), + variant.get_detailed_message().unwrap_or_default().into(), + ) + }) + .collect() + } + + fn profiles() -> Vec<(String, String)> { + Profile::VARIANTS + .iter() + .map(|variant| { + ( + variant.get_message().unwrap_or(variant.as_ref()).into(), + variant.get_detailed_message().unwrap_or_default().into(), + ) + }) + .collect() + } } From 2548d0f1aeff9b6eab13ff550fbd4af86fe9fde1 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 21 Feb 2025 16:27:40 +0100 Subject: [PATCH 23/27] refactor: remove unnecesary clones --- crates/pop-cli/src/commands/call/chain.rs | 6 +++--- crates/pop-cli/src/commands/up/parachain.rs | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 5a3aacba..5950e9b1 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -362,11 +362,11 @@ impl CallChainCommand { // Represents a chain, including its URL, client connection, and available pallets. pub(crate) struct Chain { // Websocket endpoint of the node. - pub(crate) url: Url, + pub url: Url, // The client used to interact with the chain. - pub(crate) client: OnlineClient, + pub client: OnlineClient, // A list of pallets available on the chain. - pub(crate) pallets: Vec, + pub pallets: Vec, } /// Represents a configured dispatchable function call, including the pallet, function, arguments, diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index d06e83d5..b803af0f 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -10,7 +10,7 @@ use pop_parachains::{ construct_extrinsic, extract_para_id_from_event, find_dispatchable_by_name, parse_chain_metadata, set_up_client, Action, Payload, }; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use url::Url; const DEFAULT_URL: &str = "wss://paseo.rpc.amforc.com/"; @@ -100,11 +100,11 @@ impl UpChainCommand { para_id: u32, cli: &mut impl Cli, ) -> Result<(PathBuf, PathBuf)> { - match (self.genesis_state.clone(), self.genesis_code.clone()) { - (Some(state), Some(code)) => Ok((state, code)), + match (&self.genesis_state, &self.genesis_code) { + (Some(state), Some(code)) => Ok((state.clone(), code.clone())), _ => { cli.info("Generating the chain spec for your parachain")?; - generate_spec_files(para_id, self.path.clone(), cli).await + generate_spec_files(para_id, self.path.as_deref(), cli).await }, } } @@ -170,7 +170,7 @@ fn prepare_reserve_parachain_call_data(chain: &Chain) -> Result> { // Generates chain spec files for the parachain. async fn generate_spec_files( id: u32, - path: Option, + path: Option<&Path>, cli: &mut impl Cli, ) -> anyhow::Result<(PathBuf, PathBuf)> { // Changes the working directory if a path is provided to ensure the build spec process runs in From 4656511fdb8f21a8c5374aa56422420fd49e3661 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 21 Feb 2025 16:29:01 +0100 Subject: [PATCH 24/27] refactor: minor improvements --- crates/pop-cli/src/commands/up/parachain.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index b803af0f..84a4050e 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -150,7 +150,7 @@ async fn reserve_para_id(chain: &Chain, cli: &mut impl Cli) -> Result { .await .map_err(|e| anyhow::anyhow!("Parachain ID reservation failed: {}", e))?; let para_id = extract_para_id_from_event(&events).map_err(|_| { - anyhow::anyhow!("Unable to parse the event. Specify the parachain ID manually with --id.") + anyhow::anyhow!("Unable to parse the event. Specify the parachain ID manually with `--id`.") })?; cli.success(format!("Successfully reserved parachain ID: {}", para_id))?; Ok(para_id) @@ -158,12 +158,12 @@ async fn reserve_para_id(chain: &Chain, cli: &mut impl Cli) -> Result { // Prepares and returns the encoded call data for reserving a parachain ID. fn prepare_reserve_parachain_call_data(chain: &Chain) -> Result> { - let function = find_dispatchable_by_name( + let dispatchable = find_dispatchable_by_name( &chain.pallets, Action::Reserve.pallet_name(), Action::Reserve.function_name(), )?; - let xt = construct_extrinsic(function, Vec::new())?; + let xt = construct_extrinsic(dispatchable, Vec::new())?; Ok(xt.encode_call_data(&chain.client.metadata())?) } From dcbeb6a2cc73154ac6d26b1ba2f56ea35921f4df Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 21 Feb 2025 16:44:44 +0100 Subject: [PATCH 25/27] test: refactor tests and include comments --- crates/pop-cli/src/commands/up/parachain.rs | 45 +++++++-------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 84a4050e..ce131eb5 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -240,7 +240,10 @@ mod tests { .configure_chain(&mut cli) .await?; let call_data = prepare_reserve_parachain_call_data(&chain)?; - assert_eq!(call_data, decode_call_data("0x4605")?); + // Encoded call data for a reserve extrinsic. + // Reference: https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fpolkadot.public.curie.radiumblock.co%2Fws#/extrinsics/decode/0x4605 + let encoded_reserve_extrinsic: &str = "0x4605"; + assert_eq!(call_data, decode_call_data(encoded_reserve_extrinsic)?); Ok(()) } @@ -286,19 +289,19 @@ mod tests { "Choose the chain type: ", Some(false), true, - Some(chain_types()), + Some(get_messages(ChainType::VARIANTS)), ChainType::Development as usize, ).expect_select( "Choose the relay your chain will be connecting to: ", Some(false), true, - Some(relays()), + Some(get_messages(RelayChain::VARIANTS)), RelayChain::PaseoLocal as usize, ).expect_select( "Choose the build profile of the binary that should be used: ", Some(false), true, - Some(profiles()), + Some(get_messages(Profile::VARIANTS)), Profile::Release as usize, ).expect_outro_cancel(format!("Failed to get manifest path: {}/node/Cargo.toml", fs::canonicalize(&project_path)?.display().to_string())); @@ -361,7 +364,10 @@ mod tests { chain, } .prepare_register_parachain_call_data()?; - assert_eq!(call_data, decode_call_data("0x4600d0070000081234081234")?); + // Encoded call data for a register extrinsic with the above values. + // Reference: https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fpolkadot.public.curie.radiumblock.co%2Fws#/extrinsics/decode/0x4600d0070000081234081234 + let encoded_reserve_extrinsic: &str = "0x4605"; + assert_eq!(call_data, decode_call_data(encoded_reserve_extrinsic)?); Ok(()) } @@ -377,32 +383,9 @@ mod tests { Ok((genesis_state_path, genesis_code_path)) } - fn relays() -> Vec<(String, String)> { - RelayChain::VARIANTS - .iter() - .map(|variant| { - ( - variant.get_message().unwrap_or(variant.as_ref()).into(), - variant.get_detailed_message().unwrap_or_default().into(), - ) - }) - .collect() - } - - fn chain_types() -> Vec<(String, String)> { - ChainType::VARIANTS - .iter() - .map(|variant| { - ( - variant.get_message().unwrap_or(variant.as_ref()).into(), - variant.get_detailed_message().unwrap_or_default().into(), - ) - }) - .collect() - } - - fn profiles() -> Vec<(String, String)> { - Profile::VARIANTS + // Generic helper function to convert enum variants into (message, detailed message) tuples. + fn get_messages>(variants: &[T]) -> Vec<(String, String)> { + variants .iter() .map(|variant| { ( From 3b9aace4830ce4afc4f60d6c5dccf65af63cf9e4 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 21 Feb 2025 16:52:26 +0100 Subject: [PATCH 26/27] refactor: map errors in submit_extrinsic_with_wallet --- crates/pop-cli/src/common/wallet.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/crates/pop-cli/src/common/wallet.rs b/crates/pop-cli/src/common/wallet.rs index a648285d..8eabd8fd 100644 --- a/crates/pop-cli/src/common/wallet.rs +++ b/crates/pop-cli/src/common/wallet.rs @@ -79,20 +79,16 @@ pub(crate) async fn submit_extrinsic_with_wallet( cli: &mut impl Cli, ) -> Result> { let maybe_payload = request_signature(call_data, url.to_string()).await?; - if let Some(payload) = maybe_payload { - cli.success("Signed payload received.")?; - let spinner = cliclack::spinner(); - spinner.start( - "Submitting the extrinsic and then waiting for finalization, please be patient...", - ); + let payload = maybe_payload.ok_or_else(|| anyhow!("No signed payload received."))?; + cli.success("Signed payload received.")?; + let spinner = cliclack::spinner(); + spinner + .start("Submitting the extrinsic and then waiting for finalization, please be patient..."); - let result = submit_signed_extrinsic(client.clone(), payload) - .await - .map_err(|err| anyhow!("{}", format!("{err:?}")))?; + let result = submit_signed_extrinsic(client.clone(), payload) + .await + .map_err(anyhow::Error::from)?; - spinner.stop(format!("Extrinsic submitted with hash: {:?}", result.extrinsic_hash())); - Ok(result) - } else { - Err(anyhow!("No signed payload received.")) - } + spinner.stop(format!("Extrinsic submitted with hash: {:?}", result.extrinsic_hash())); + Ok(result) } From 8bad4304eb17fd28fe161b294c694cf4dc75afa1 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 21 Feb 2025 17:05:23 +0100 Subject: [PATCH 27/27] test: fix prepare_register_parachain_call_data_works --- crates/pop-cli/src/commands/up/parachain.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index ce131eb5..7339f08f 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -366,8 +366,8 @@ mod tests { .prepare_register_parachain_call_data()?; // Encoded call data for a register extrinsic with the above values. // Reference: https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fpolkadot.public.curie.radiumblock.co%2Fws#/extrinsics/decode/0x4600d0070000081234081234 - let encoded_reserve_extrinsic: &str = "0x4605"; - assert_eq!(call_data, decode_call_data(encoded_reserve_extrinsic)?); + let encoded_register_extrinsic: &str = "0x4600d0070000081234081234"; + assert_eq!(call_data, decode_call_data(encoded_register_extrinsic)?); Ok(()) }