Skip to content

Commit 4f594c4

Browse files
authored
feat: logic for parachain registration (#410)
* feat: deploy a parachain commands * feat: build specs in pop up * feat: logic to interact with a chain * feat: register parachain * refactor: clean code and improve docs * test: unit tests for pop up methods * refactor: small fixes with visibility and removing logs * feat: return events in submit_signed_extrinsic * feat: get para_id from event * test: fix detects_parachain_correctly * refactor: improve docs and code * test: fix change_working_directory_works * fix: clippy warnings * refactor: move submit_extrinsic_with_wallet in a common file * refactor: remove unnecesary code * refactor: UpChainCommand structure * test: adjust tests to refactored struct * refactor: renaming prepare_register_parachain_call_data and prepare_rerve_parachain_call_data * refactor: move events module * fix: submit_extrinsic_with_wallet under parachain feature * refactor: remove unnecesary code * test: increase coverage with reserve_parachain_id_fails_wrong_chain and resolve_genesis_files_fails_wrong_path * refactor: remove unnecesary clones * refactor: minor improvements * test: refactor tests and include comments * refactor: map errors in submit_extrinsic_with_wallet * test: fix prepare_register_parachain_call_data_works * refactor: move configure_chain into a common folder * refactor: function visibility * fix: error message and include test for it * refactor: build specs removing repetitive code * refactor: use prepare_extrinsic from Call module to prepare a call * docs: improve comments and messages * refactor: rename variables and structs * refactor: relay_chain_url * refactor: rename prepare_reserve_call_data and prepare_register_call_data * test: remove unnecesary test * refactor: remove events module * refactor: rename parachain to rollup * chore: improve succesful message * chore: change intro title to use rollup * docs: comments for Reserved event * refactor: improve readability with chain::configure * refactor: improve message and clean code
1 parent 0f53ec6 commit 4f594c4

File tree

14 files changed

+558
-121
lines changed

14 files changed

+558
-121
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ subxt = "0.38.0"
4949
ink_env = "5.0.0"
5050
sp-core = "32.0.0"
5151
sp-weights = "31.0.0"
52+
scale = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] }
5253
scale-info = { version = "2.11.4", default-features = false, features = ["derive"] }
5354
scale-value = { version = "0.17.0", default-features = false, features = ["from-string", "parser-ss58"] }
5455
contract-build = "5.0.2"

crates/pop-cli/src/commands/build/spec.rs

+22-10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ use std::{thread::sleep, time::Duration};
2525
use strum::{EnumMessage, VariantArray};
2626
use strum_macros::{AsRefStr, Display, EnumString};
2727

28+
pub(crate) type CodePathBuf = PathBuf;
29+
pub(crate) type StatePathBuf = PathBuf;
30+
2831
const DEFAULT_CHAIN: &str = "dev";
2932
const DEFAULT_PARA_ID: u32 = 2000;
3033
const DEFAULT_PROTOCOL_ID: &str = "my-protocol";
@@ -179,21 +182,21 @@ impl BuildSpecCommand {
179182
// Checks for appchain project in `./`.
180183
if is_supported(None)? {
181184
let build_spec = self.configure_build_spec(&mut cli).await?;
182-
build_spec.build(&mut cli)
185+
build_spec.build(&mut cli)?;
183186
} else {
184187
cli.outro_cancel(
185188
"🚫 Can't build a specification for target. Maybe not a chain project ?",
186189
)?;
187-
Ok("spec")
188190
}
191+
Ok("spec")
189192
}
190193

191194
/// Configure chain specification requirements by prompting for missing inputs, validating
192195
/// provided values, and preparing a BuildSpec to generate file(s).
193196
///
194197
/// # Arguments
195198
/// * `cli` - The cli.
196-
async fn configure_build_spec(
199+
pub(crate) async fn configure_build_spec(
197200
self,
198201
cli: &mut impl cli::traits::Cli,
199202
) -> anyhow::Result<BuildSpec> {
@@ -439,7 +442,7 @@ impl BuildSpecCommand {
439442

440443
// Represents the configuration for building a chain specification.
441444
#[derive(Debug)]
442-
struct BuildSpec {
445+
pub(crate) struct BuildSpec {
443446
output_file: PathBuf,
444447
profile: Profile,
445448
id: u32,
@@ -458,7 +461,10 @@ impl BuildSpec {
458461
// This function generates plain and raw chain spec files based on the provided configuration,
459462
// optionally including genesis state and runtime artifacts. If the node binary is missing,
460463
// it triggers a build process.
461-
fn build(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> {
464+
pub(crate) fn build(
465+
self,
466+
cli: &mut impl cli::traits::Cli,
467+
) -> anyhow::Result<(Option<CodePathBuf>, Option<StatePathBuf>)> {
462468
cli.intro("Building your chain spec")?;
463469
let mut generated_files = vec![];
464470
let BuildSpec {
@@ -500,21 +506,27 @@ impl BuildSpec {
500506
));
501507

502508
// Generate genesis artifacts.
503-
if genesis_code {
509+
let genesis_code_file = if genesis_code {
504510
spinner.set_message("Generating genesis code...");
505511
let wasm_file_name = format!("para-{}.wasm", id);
506512
let wasm_file = export_wasm_file(&binary_path, &raw_chain_spec, &wasm_file_name)?;
507513
generated_files
508514
.push(format!("WebAssembly runtime file exported at: {}", wasm_file.display()));
509-
}
510-
if genesis_state {
515+
Some(wasm_file)
516+
} else {
517+
None
518+
};
519+
let genesis_state_file = if genesis_state {
511520
spinner.set_message("Generating genesis state...");
512521
let genesis_file_name = format!("para-{}-genesis-state", id);
513522
let genesis_state_file =
514523
generate_genesis_state_file(&binary_path, &raw_chain_spec, &genesis_file_name)?;
515524
generated_files
516525
.push(format!("Genesis State file exported at: {}", genesis_state_file.display()));
517-
}
526+
Some(genesis_state_file)
527+
} else {
528+
None
529+
};
518530

519531
spinner.stop("Chain specification built successfully.");
520532
let generated_files: Vec<_> = generated_files
@@ -526,7 +538,7 @@ impl BuildSpec {
526538
"Need help? Learn more at {}\n",
527539
style("https://learn.onpop.io").magenta().underlined()
528540
))?;
529-
Ok("spec")
541+
Ok((genesis_code_file, genesis_state_file))
530542
}
531543

532544
// Customize a chain specification.

crates/pop-cli/src/commands/call/chain.rs

+59-101
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@ use std::path::Path;
44

55
use crate::{
66
cli::{self, traits::*},
7-
common::wallet::{prompt_to_use_wallet, request_signature},
7+
common::{
8+
chain::{self, Chain},
9+
wallet::{self, prompt_to_use_wallet},
10+
},
811
};
912
use anyhow::{anyhow, Result};
1013
use clap::Args;
1114
use pop_parachains::{
1215
construct_extrinsic, construct_sudo_extrinsic, decode_call_data, encode_call_data,
13-
find_dispatchable_by_name, find_pallet_by_name, parse_chain_metadata, set_up_client,
14-
sign_and_submit_extrinsic, submit_signed_extrinsic, supported_actions, Action, CallData,
15-
DynamicPayload, Function, OnlineClient, Pallet, Param, Payload, SubstrateConfig,
16+
find_dispatchable_by_name, find_pallet_by_name, sign_and_submit_extrinsic, supported_actions,
17+
Action, CallData, DynamicPayload, Function, OnlineClient, Pallet, Param, Payload,
18+
SubstrateConfig,
1619
};
1720
use url::Url;
1821

@@ -67,10 +70,17 @@ impl CallChainCommand {
6770
/// Executes the command.
6871
pub(crate) async fn execute(mut self) -> Result<()> {
6972
let mut cli = cli::Cli;
73+
cli.intro("Call a chain")?;
7074
// Check if all fields are specified via the command line.
7175
let prompt_to_repeat_call = self.requires_user_input();
7276
// Configure the chain.
73-
let chain = self.configure_chain(&mut cli).await?;
77+
let chain = chain::configure(
78+
"Which chain would you like to interact with?",
79+
DEFAULT_URL,
80+
&self.url,
81+
&mut cli,
82+
)
83+
.await?;
7484
// Execute the call if call_data is provided.
7585
if let Some(call_data) = self.call_data.as_ref() {
7686
if let Err(e) = self
@@ -109,7 +119,9 @@ impl CallChainCommand {
109119
// Sign and submit the extrinsic.
110120
let result = if self.use_wallet {
111121
let call_data = xt.encode_call_data(&chain.client.metadata())?;
112-
submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, &mut cli).await
122+
wallet::submit_extrinsic(&chain.client, &chain.url, call_data, &mut cli)
123+
.await
124+
.map(|_| ()) // Mapping to `()` since we don't need events returned
113125
} else {
114126
call.submit_extrinsic(&chain.client, &chain.url, xt, &mut cli).await
115127
};
@@ -132,33 +144,6 @@ impl CallChainCommand {
132144
Ok(())
133145
}
134146

135-
// Configures the chain by resolving the URL and fetching its metadata.
136-
async fn configure_chain(&self, cli: &mut impl Cli) -> Result<Chain> {
137-
cli.intro("Call a chain")?;
138-
// Resolve url.
139-
let url = match &self.url {
140-
Some(url) => url.clone(),
141-
None => {
142-
// Prompt for url.
143-
let url: String = cli
144-
.input("Which chain would you like to interact with?")
145-
.default_input(DEFAULT_URL)
146-
.interact()?;
147-
Url::parse(&url)?
148-
},
149-
};
150-
151-
// Parse metadata from chain url.
152-
let client = set_up_client(url.as_str()).await?;
153-
let mut pallets = parse_chain_metadata(&client).map_err(|e| {
154-
anyhow!(format!("Unable to fetch the chain metadata: {}", e.to_string()))
155-
})?;
156-
// Sort by name for display.
157-
pallets.sort_by(|a, b| a.name.cmp(&b.name));
158-
pallets.iter_mut().for_each(|p| p.functions.sort_by(|a, b| a.name.cmp(&b.name)));
159-
Ok(Chain { url, client, pallets })
160-
}
161-
162147
// Configure the call based on command line arguments/call UI.
163148
fn configure_call(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result<Call> {
164149
loop {
@@ -244,7 +229,7 @@ impl CallChainCommand {
244229
if use_wallet {
245230
let call_data_bytes =
246231
decode_call_data(call_data).map_err(|err| anyhow!("{}", format!("{err:?}")))?;
247-
submit_extrinsic_with_wallet(client, url, call_data_bytes, cli)
232+
wallet::submit_extrinsic(client, url, call_data_bytes, cli)
248233
.await
249234
.map_err(|err| anyhow!("{}", format!("{err:?}")))?;
250235
display_message("Call complete.", true, cli)?;
@@ -357,41 +342,31 @@ impl CallChainCommand {
357342
}
358343
}
359344

360-
// Represents a chain, including its URL, client connection, and available pallets.
361-
struct Chain {
362-
// Websocket endpoint of the node.
363-
url: Url,
364-
// The client used to interact with the chain.
365-
client: OnlineClient<SubstrateConfig>,
366-
// A list of pallets available on the chain.
367-
pallets: Vec<Pallet>,
368-
}
369-
370345
/// Represents a configured dispatchable function call, including the pallet, function, arguments,
371346
/// and signing options.
372-
#[derive(Clone)]
373-
struct Call {
347+
#[derive(Clone, Default)]
348+
pub(crate) struct Call {
374349
/// The dispatchable function to execute.
375-
function: Function,
350+
pub(crate) function: Function,
376351
/// The dispatchable function arguments, encoded as strings.
377-
args: Vec<String>,
352+
pub(crate) args: Vec<String>,
378353
/// Secret key URI for the account signing the extrinsic.
379354
///
380355
/// e.g.
381356
/// - for a dev account "//Alice"
382357
/// - with a password "//Alice///SECRET_PASSWORD"
383-
suri: String,
358+
pub(crate) suri: String,
384359
/// Whether to use your browser wallet to sign the extrinsic.
385-
use_wallet: bool,
360+
pub(crate) use_wallet: bool,
386361
/// Whether to automatically sign and submit the extrinsic without prompting for confirmation.
387-
skip_confirm: bool,
362+
pub(crate) skip_confirm: bool,
388363
/// Whether to dispatch the function call with `Root` origin.
389-
sudo: bool,
364+
pub(crate) sudo: bool,
390365
}
391366

392367
impl Call {
393368
// Prepares the extrinsic.
394-
fn prepare_extrinsic(
369+
pub(crate) fn prepare_extrinsic(
395370
&self,
396371
client: &OnlineClient<SubstrateConfig>,
397372
cli: &mut impl Cli,
@@ -473,32 +448,6 @@ impl Call {
473448
}
474449
}
475450

476-
// Sign and submit an extrinsic using wallet integration.
477-
async fn submit_extrinsic_with_wallet(
478-
client: &OnlineClient<SubstrateConfig>,
479-
url: &Url,
480-
call_data: Vec<u8>,
481-
cli: &mut impl Cli,
482-
) -> Result<()> {
483-
let maybe_payload = request_signature(call_data, url.to_string()).await?;
484-
if let Some(payload) = maybe_payload {
485-
cli.success("Signed payload received.")?;
486-
let spinner = cliclack::spinner();
487-
spinner.start(
488-
"Submitting the extrinsic and then waiting for finalization, please be patient...",
489-
);
490-
491-
let result = submit_signed_extrinsic(client.clone(), payload)
492-
.await
493-
.map_err(|err| anyhow!("{}", format!("{err:?}")))?;
494-
495-
spinner.stop(format!("Extrinsic submitted with hash: {:?}", result));
496-
} else {
497-
display_message("No signed payload received.", false, cli)?;
498-
}
499-
Ok(())
500-
}
501-
502451
// Displays a message to the user, with formatting based on the success status.
503452
fn display_message(message: &str, success: bool, cli: &mut impl Cli) -> Result<()> {
504453
if success {
@@ -670,33 +619,20 @@ fn parse_function_name(name: &str) -> Result<String, String> {
670619
mod tests {
671620
use super::*;
672621
use crate::{cli::MockCli, common::wallet::USE_WALLET_PROMPT};
622+
use pop_parachains::{parse_chain_metadata, set_up_client};
673623
use tempfile::tempdir;
674624
use url::Url;
675625

676626
const BOB_SURI: &str = "//Bob";
677627
const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz";
678628
const POLKADOT_NETWORK_URL: &str = "wss://polkadot-rpc.publicnode.com";
679629

680-
#[tokio::test]
681-
async fn configure_chain_works() -> Result<()> {
682-
let call_config =
683-
CallChainCommand { suri: Some(DEFAULT_URI.to_string()), ..Default::default() };
684-
let mut cli = MockCli::new().expect_intro("Call a chain").expect_input(
685-
"Which chain would you like to interact with?",
686-
POP_NETWORK_TESTNET_URL.into(),
687-
);
688-
let chain = call_config.configure_chain(&mut cli).await?;
689-
assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?);
690-
cli.verify()
691-
}
692-
693630
#[tokio::test]
694631
async fn guide_user_to_call_chain_works() -> Result<()> {
695632
let mut call_config =
696633
CallChainCommand { pallet: Some("System".to_string()), ..Default::default() };
697634

698635
let mut cli = MockCli::new()
699-
.expect_intro("Call a chain")
700636
.expect_input("Which chain would you like to interact with?", POP_NETWORK_TESTNET_URL.into())
701637
.expect_select(
702638
"Select the function to call:",
@@ -724,7 +660,13 @@ mod tests {
724660
.expect_confirm("Would you like to dispatch this function call with `Root` origin?", true)
725661
.expect_confirm(USE_WALLET_PROMPT, true);
726662

727-
let chain = call_config.configure_chain(&mut cli).await?;
663+
let chain = chain::configure(
664+
"Which chain would you like to interact with?",
665+
POP_NETWORK_TESTNET_URL,
666+
&None,
667+
&mut cli,
668+
)
669+
.await?;
728670
assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?);
729671

730672
let call_chain = call_config.configure_call(&chain, &mut cli)?;
@@ -742,11 +684,17 @@ mod tests {
742684
async fn guide_user_to_configure_predefined_action_works() -> Result<()> {
743685
let mut call_config = CallChainCommand::default();
744686

745-
let mut cli = MockCli::new().expect_intro("Call a chain").expect_input(
687+
let mut cli = MockCli::new().expect_input(
746688
"Which chain would you like to interact with?",
747689
POLKADOT_NETWORK_URL.into(),
748690
);
749-
let chain = call_config.configure_chain(&mut cli).await?;
691+
let chain = chain::configure(
692+
"Which chain would you like to interact with?",
693+
POP_NETWORK_TESTNET_URL,
694+
&None,
695+
&mut cli,
696+
)
697+
.await?;
750698
assert_eq!(chain.url, Url::parse(POLKADOT_NETWORK_URL)?);
751699
cli.verify()?;
752700

@@ -896,20 +844,30 @@ mod tests {
896844
sudo: true,
897845
};
898846
let mut cli = MockCli::new()
899-
.expect_intro("Call a chain")
900847
.expect_warning("NOTE: sudo is not supported by the chain. Ignoring `--sudo` flag.");
901-
let chain = call_config.configure_chain(&mut cli).await?;
848+
let chain = chain::configure(
849+
"Which chain would you like to interact with?",
850+
POP_NETWORK_TESTNET_URL,
851+
&Some(Url::parse(POLKADOT_NETWORK_URL)?),
852+
&mut cli,
853+
)
854+
.await?;
902855
call_config.configure_sudo(&chain, &mut cli)?;
903856
assert!(!call_config.sudo);
904857
cli.verify()?;
905858

906859
// Test when sudo pallet exist.
907-
cli = MockCli::new().expect_intro("Call a chain").expect_confirm(
860+
cli = MockCli::new().expect_confirm(
908861
"Would you like to dispatch this function call with `Root` origin?",
909862
true,
910863
);
911-
call_config.url = Some(Url::parse(POP_NETWORK_TESTNET_URL)?);
912-
let chain = call_config.configure_chain(&mut cli).await?;
864+
let chain = chain::configure(
865+
"Which chain would you like to interact with?",
866+
POP_NETWORK_TESTNET_URL,
867+
&Some(Url::parse(POP_NETWORK_TESTNET_URL)?),
868+
&mut cli,
869+
)
870+
.await?;
913871
call_config.configure_sudo(&chain, &mut cli)?;
914872
assert!(call_config.sudo);
915873
cli.verify()

0 commit comments

Comments
 (0)